R Notebooks

Este é um R Notebook. Quando você executa o código R no notebook, os resultados aparecem abaixo do código.

O código R precisa estar em “pedaços” (chunks) em um Notebook R. Logo abaixo, está um exemplo de um pedaço de código R. O código produz uma parábola.

Tente executar este cógdigo, colocando o cursor dentro do código e clicando no botão Run, ou colocando o cursor dentro do código e pressionando Ctrl+Shift+Enter (Windows/Linux).

x <- seq(-1, 1, by = 0.01)
y <- x^2
plot(x, y, type = "l")

Para ocultar a saída, clique nas setas para expandir/recolher a saída. Para limpar os resultados (ou um erro), clique no botão “x”.

Você também pode pressionar Ctrl+Enter (Win/Linux) para executar uma linha de código por vez (em vez de todo o bloco).

Adicione um novo pedaço de código R clicando no botão Insert new code chunk na barra de ferramentas ou pressionando Ctrl+Alt+I (Win/Linux).

Questão 1

Insira um novo pedaço de código R abaixo, digite e execute o código: Sys.time()

Modelos de Regressão Linear com Dados Simulados

Em vez de usar teoria e fórmulas, vamos explorar e revisar a modelaos de regressão linear usando dados simulados.

O código abaixo faz o seguinte:

  1. atribui a x os valores entre 1 e 25.

  2. Em seguida, geramos y como função de x usando a fórmula 10 + 5*x

  3. criamos a data frame d para armazenar os valores de x e y

  4. fazemos um gráfico de dispersão simples entre y e x.

x <- 1:25
y <- 10 + 5*x  # formula for a line
d <- data.frame(x, y)
plot(y ~ x, data = d)

Agora vamos adicionar algum “ruído” aos nossos dados adicionando valores (pseudo) aleatórios de uma distribuição Normal com média = 0 e desvio padrão = 10.

A função rnorm() nos permite extrair valores aleatórios de uma distribuição Normal.

O comando set.seed(1) garante que todos geremos os mesmos dados “aleatórios”:

set.seed(1)
erro <- rnorm(n = 25, mean = 0, sd = 10)
# Adiciona erro aleatorio a 10 + 5*x e refaz o grafico de dispersao
d$y <- 10 + 5*x + erro
plot(y ~ x, data = d)

Agora y parece estar associado a x, mas não completamente determinado por x.

y é a combinação de uma parte fixa e uma parte aleatória:

  1. parte fixa: 10 + 5*x
  2. parte aleatória: rnorm(n = 25, mean = 0, sd = 10)

Questão 2

E se recebêssemos esses dados e nos dissessem para determinar o processo que os gerou? Em outras palavras, trabalhe de trás para frente e preencha os espaços em branco:

  1. ___ + ___*x
  2. rnorm(n = 25, mean = 0, sd = ____)

Essa é a abordagem tradicional em modelagem/regressão linear. Você tem alguma variável resposta numérica e deseja encontrar o modelo que gerou os dados.

A modelagem de regressão linear múltipla tradicional assume as seguintes hipóteses como verdadeiras (entre outras):

  1. a fórmula é uma soma ponderada de preditores (por exemplo, y = 10 + 5*x).

  2. o erro é uma realização aleatória de uma distribuição Normal com média = 0.

  3. o desvio padrão da distribuição Normal é constante (por exemplo, 10)

Os modelos de regressão linear tentam estimar os “pesos” da primeira hipótese e o desvio padrão da terceira hipótese.

Vamos demonstrar: Abaixo tentamos recuperar o processo gerador para nossos dados. Para isso usamos a função lm(). Temos que especificar a fórmula para a primeira hipótese. A segunda e a terceira hipóteses estão incorporadas em lm().

A fórmula “y ~ x” significa que pensamos que o modelo é “y = intercepto + inclinação*x” ou (“b0 + b1x”).

A menos que especifiquemos o contrário, isso pressupõe que queremos estimar o intercepto. Isso diz à função lm() para pegar os dados e encontrar os melhores intercepto e coeficiente de inclinação. Observe que este é o modelo correto!

Quando você usa lm() você geralmente deseja salvar os resultados em um objeto. Abaixo salvamos no objeto “mod_sim”. Em seguida, visualizamos os resultados do modelo usando summary():

mod_sim <- lm(y ~ x, data = d)
summary(mod_sim)

Call:
lm(formula = y ~ x, data = d)

Residuals:
    Min      1Q  Median      3Q     Max 
-23.876  -4.613   2.254   5.909  14.648 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   11.135      3.999   2.784   0.0105 *  
x              5.042      0.269  18.743 1.98e-15 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 9.7 on 23 degrees of freedom
Multiple R-squared:  0.9386,    Adjusted R-squared:  0.9359 
F-statistic: 351.3 on 1 and 23 DF,  p-value: 1.981e-15

O modelo retorna as seguintes estimativas:

  1. y = 11.135 + 5.042 * x

  2. erro = rnorm(n = 25, mean = 0, sd = 9.7)

Eles estão bem próximos dos valores “verdadeiros” de 10, 5 e 10 que usamos para simular os dados.

Na vida real, NÃO CONHECEMOS a fórmula da parte 1. O verdadeiro processo gerador dos dados será muito mais complicado. A fórmula que propomos será apenas uma aproximação e pode não ser boa.

Na vida real, NÃO SABEMOS se a suposição de normalidade ou suposição de variância constante do ruído são plausíveis.

Como podemos avaliar nosso modelo?

Poderíamos usar nossas estimativas dos parâmetros do modelo para gerar dados e ver se eles se parecem com nossos dados originais.

Execute o código abaixo de uma vez e mais de uma vez. Os pontos pretos não mudam, mas os vermelhos sim. Isso parece muito bom! Nossos dados gerados pelo modelo parecem semelhantes aos nossos dados observados.

# o modelo simulado original é: d$y <- 10 + 5*x + erro
d$y2 <- 11.135 + 5.042*d$x + rnorm(25, 0, 9.7)
plot(y ~ x, data = d) # dados originais 
points(d$x, d$y2, col = "red") # dados simulados

Também podemos comparar curvas de densidade suaves dos dados originais e com os gerados pelo modelo. As curvas de densidade suaves são basicamente versões suaves de histogramas.

Se tivermos um bom modelo, os dados gerados pelo nosso modelo deverão ter uma distribuição semelhante aos dados originais. Execute o código abaixo de uma vez e mais de uma vez.

hist(d$y, freq = FALSE) # freq = FALSE -> area das barras somam 1
lines(density(d$y))  # dados originais
d$y2 <- 11.135 + 5.042*d$x + rnorm(25, 0, 9.7)
lines(density(d$y2), col = "red")  # dados simulados

O resultado parece bom. A distribuição dos dados gerados pelo nosso modelo é muito semelhante aos dados observados. Você deve fazer isso mais de uma vez, digamos 50 vezes, para garantir que o modelo gere consistentemente dados semelhantes aos observados. Mostraremos uma maneira mais eficiente de fazer essa simulação mais adiante.

Como achamos que nosso modelo é “bom”, podemos usá-lo para fazer uma previsão. Por exemplo, quando x = 10 qual é o valor esperado de y? Dito de outra forma, qual é a média de y condicional a x = 10?

Podemos obter essa previsão com a função predict(). O argumento interval = "confidence" signififca que desejamos uma estimativa por intervalo com 95% de confiança (IC) para esta média condicional.

predict(mod_sim, newdata = data.frame(x = 10), interval = "confidence")
       fit     lwr      upr
1 61.55932 57.2126 65.90603

A média esperada (condicional) de y quando x = 10 é de cerca de 61,6 com um IC de 95% de (57,2, 65,9). O IC nos dá uma noção de quão incerta é essa média esperada. Na verdade, seria melhor relatar isso como “a média esperada de y quando x = 10 deve estar entre 57 a 66”.

Poderíamos também tentar resumir a relação entre y e x examinando os coeficientes (ou pesos) obtidos no sumário dos resultados. Podemos extrair os coeficientes do sumário usando a função coef():

coef(summary(mod_sim))
             Estimate Std. Error   t value     Pr(>|t|)
(Intercept) 11.134859  3.9994866  2.784072 1.054874e-02
x            5.042446  0.2690346 18.742742 1.981382e-15

O coeficiente x diz que y aumenta cerca de 5 unidades para cada aumento de uma unidade em x, variando em média 0,27 acima ou abaixo de 5. O erro padrão nos dá uma indicação da incerteza nesta estimativa. Falaremos mais sobre os valores t e valores-p adiante.

**RESUMO:**

Essencialmente, a modelagem de regressão linear básica consistem em:

  1. propor e ajustar um modelo linear

  2. determinar se o modelo é bom e se as premissas são atendidas em sua maioria.

  3. usar o modelo para explicar a relação entre y e x e/ou fazer previsões.

Vamos ver o que acontece quando ajustamos um modelo “ruim”. Abaixo, adicionamos uma nova coluna à data frame d chamada z, que é uma amostra aleatória de números no intervalo de -100 a 100. runif() amostra números de uma distribuição uniforme entre min e max.

set.seed(4)
d$z <- runif(25, min = -100, max = 100)

LEMBRETE: É possívei adicionar um novo trecho de código R clicando no botão Insert new code chunk na barra de ferramentas ou pressionando Ctrl+Alt+I (Win/Linux).

Questão 3

  1. Modele y como uma função de z usando lm(y ~ z, data = d) e salve os resultados em um objeto chamado mod_sim2, veja os resultados usando a função summary(). Qual modelo foi estimado? Qual é o desvio padrão estimado do erro aleatório normalmente distribuído?

  2. Use o modelo para simular um histogramas ou densidade e compare com o histograma e densidade original de d$y. Execute o código várias vezes para ver como a curva de densidade gerada pelo modelo varia.

Agora, vamos aplicar um modelo de regressão linear a dados reais.

Importando os Dados

Vamos importar os dados que usaremos . Os dados com os quais trabalharemos são dados imobiliários do condado de Albemarle no estado da Virgínia/EUA, baixados do Office of Geographic Data Services. Usaremos uma amostra aleatória dos dados.

library(readr)
url <- 'https://raw.githubusercontent.com/clayford/dataviz_with_ggplot2/master/alb_homes.csv'
homes <- readr::read_csv(file = url)
dplyr::glimpse(homes)
Rows: 3,025
Columns: 15
$ yearbuilt   <dbl> 1754, 1968, 1754, 1934, 1963, 1754, 1932, 1960, 1950,…
$ finsqft     <dbl> 1254, 1192, 881, 480, 720, 990, 792, 1232, 576, 863, …
$ cooling     <chr> "No Central Air", "No Central Air", "No Central Air",…
$ bedroom     <dbl> 1, 3, 2, 0, 2, 3, 2, 3, 2, 0, 2, 1, 2, 3, 3, 3, 4, 4,…
$ fullbath    <dbl> 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 2, 1, 2,…
$ halfbath    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1,…
$ lotsize     <dbl> 4.9330, 1.0870, 195.9300, 10.0000, 1.0000, 1.0700, 4.…
$ totalvalue  <dbl> 124300, 109200, 141600, 69200, 139700, 67400, 237500,…
$ esdistrict  <chr> "Brownsville", "Scottsville", "Stony Point", "Crozet"…
$ msdistrict  <chr> "Henley", "Walton", "Sutherland", "Henley", "Henley",…
$ hsdistrict  <chr> "Western Albemarle", "Monticello", "Albemarle", "West…
$ censustract <dbl> 111.00, 113.01, 104.01, 101.00, 102.02, 112.01, 101.0…
$ age         <dbl> 265, 51, 265, 85, 56, 265, 87, 59, 69, 265, 71, 265, …
$ condition   <chr> "Substandard", "Substandard", "Substandard", "Substan…
$ fp          <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,…

Vejamos as primeiras 6 linhas:

head(homes)

Dicionário dos Dados:

Regressão Linear com Dados Imobiliários

Digamos que queremos modelar o valor total médio de uma casa (representado pela variável totalvalue) em função de várias características, como tamanho do lote, metros quadrados construídos, presença de ar central, etc.

Vamos ver como a distribuição da variável que representa o valor total usando um histograma. Observe que a distribuição é bastante assimétrica:

hist(homes$totalvalue)

Podemos também criar uma curva de densidade, que é uma versão suavizada de um histograma:

plot(density(homes$totalvalue))

Para modelar o valor médio total das residências em função de diversas características, precisamos propor um modelo linear. Ao contrário do exemplo anterior, estes não são dados simulados para os quais conhecemos o processo gerador dos dados.

Como propor um modelo? É fundamental ter algum conhecimento sobre o fenômeno

Vamos ajustar um modelo linear usando pés quadrados (finsqft), o número de quartos (bedroom) e tamanho do lote (lotsize). O sinal de mais (+) significa “incluir” no modelo.

m1 <- lm(totalvalue ~ finsqft + bedroom + lotsize, data = homes)
summary(m1)

Call:
lm(formula = totalvalue ~ finsqft + bedroom + lotsize, data = homes)

Residuals:
     Min       1Q   Median       3Q      Max 
-1164152   -82296    -7690    57164  5879188 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) -133328.25   16273.88  -8.193 3.73e-16 ***
finsqft         284.46       5.37  52.967  < 2e-16 ***
bedroom      -13218.41    5750.70  -2.299   0.0216 *  
lotsize        4268.77     219.32  19.464  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 227200 on 3021 degrees of freedom
Multiple R-squared:  0.6164,    Adjusted R-squared:  0.616 
F-statistic:  1618 on 3 and 3021 DF,  p-value: < 2.2e-16

A função coef() extrai os coeficientes, ou parâmetros estimados:

coef(m1)
 (Intercept)      finsqft      bedroom      lotsize 
-133328.2482     284.4613  -13218.4091    4268.7655 

Com os parâmetros estimados, podemos escreve o modelo estimado:

totalprice = -133328.2482 + 284.4613*finsqft + -13218.4091*bedroom +
               4268.7655*lotsize`

Algumas interpretações básicas:

Cada uma dessas interpretações assume que todas as outras variáveis são mantidas constantes! Portanto, estima-se que adicionar um quarto a uma casa, sem aumentar o tamanho do lote ou os metros quadrados acabados da casa, reduza o valor da casa. Isso faz sentido?

Este é um modelo “bom”? Vamos simular os dados do modelo e compará-los com os dados observados. Um “bom” modelo deve gerar dados semelhantes aos dados originais.

Poderíamos fazer isso manualmente:

sim_y <- -133328.2482 + 284.4613*homes$finsqft + 
  -13218.4091*homes$bedroom + 4268.7655*homes$lotsize + 
  rnorm(3025, sd = 227200)

Uma maneira mais fácil e rápida é usar a função simulate() que permite simular múltiplas amostras. Aqui geramos 50 amostras. Cada amostra terá o mesmo número de observações que nossa amostra original (n = 3.025). Cada valor de amostra é gerado usando nossos valores observados para finsqft, bedroom e lotsize. O resultado é uma data frame com 50 colunas.

sim1 <- simulate(m1, nsim = 50)

Agora vamos representar graficamente os dados simulados e os dados observados usando gráficos de densidade. Usamos um loop for para adicionar estimativas de densidade suaves das 50 simulações.

O comando sim1[[i]] extrai a coluna i como um vetor. (execute o código de uma vez.)

plot(density(homes$totalvalue))
for(i in 1:50)lines(density(sim1[[i]]), col = "grey80")

(Veja o final do notebook para saber como criar esse gráfico usando o pacote ggplot2 e como transformar esse código em uma função.)

Este não parece ser um bom modelo. Na verdade alguns dos nossos valores simulados são negativos!

Antes de revisarmos o modelo, lembre-se das principais hipóteses:

  1. totalvalue pode ser modelado por uma soma ponderada: valor total = interceptação + pés quadrados + quartos + tamanho do lote
  2. O erro aleatóri segue aproximadamente uma distribuição Normal com média 0.
  3. O desvio-padrão (variância) desta distribuição Normal é constante

A linguagem R fornece alguns gráficos de diagnóstico básicos para avaliar as hipóteses 2 e 3 sobre os resíduos. Basta aplicar a função plot no objeto que armazenou os resultados da estimação do modelo.

plot(m1)

Como interpretar os gráficos:

  1. Resíduos vs Valores Ajustados: deve possuir uma linha horizontal com dispersão uniforme e simétrica dos pontos; se não, haverá evidência de que a variância ou desvio-padrão não é constante.

  2. QQ normal: os pontos devem ficar próximos à linha diagonal; caso contrário, haverá evidência de que o resíduo não segue, aproximadamente, uma distribuição n=Normal.

  3. Scale-Location: deve ter uma linha horizontal com dispersão uniforme de pontos; (semelhante ao nº 1, mas mais fácil de detectar tendência na dispersão).

  4. Residuais vs Alavancagem: pontos fora das curvas de nível são observações influentes. Alavancagem é a distância do centro de todos os preditores. Uma observação com alta alavancagem tem influência substancial no valor ajustado.

Por padrão, os 3 pontos “mais extremos” são rotulados pelo número da linha. 2658 aparece em todos as quatro gráficos. É uma casa muito grande e cara.

homes[2658,]

Esses gráficos revelam que nossas suposições sobre a normalidade e variância constante dos resíduos são altamente suspeitas. Nosso modelo é simplesmente ruim.

O que podemos fazer?

A variação não constante pode ser evidência de um modelo errado ou de uma variável resposta muito assimétrica (ou um pouco de ambos). Lembre-se de que a variável resposta é bastante assimétrica:

hist(homes$totalvalue)

Ao lidar com uma variável resposta estritamente positiva e muito assimétrica Ao lidar com uma resposta estritamente positiva e muito distorcida (como variáveis que representam preços), é comum transformar a variável resposta para uma escala diferente.

Uma transformação comum é uma transformação logarítmica. Quando aplicamos o logaritmo natural a variável totalvalue, a distribuição parece um pouco mais simétrica, embora seja importante observar que isso não é uma suposição da modelagem linear!

hist(log(homes$totalvalue))

Vamos tentar modelar a variável totalvalue log-transformada.

m2 <- lm(log(totalvalue) ~ finsqft + bedroom + lotsize, data = homes)

Os gráficos de diagnóstico dos resóduos parecem melhores.

plot(m2)

Mas este é um “bom modelo”? Nosso modelo proposto de somas ponderadas é bom? Novamente, vamos simular dados e comparar com os dados observados.

sim2 <- simulate(m2, nsim = 50)
plot(density(log(homes$totalvalue)))
for(i in 1:50)lines(density(sim2[[i]]), lty = 2, col = "grey80")

Os resultados não são tão ruins!

Digamos que estejamos satisfeitos com este modelo. Como interpretamos as estimativas dos parâmetros? Como a variável resposta foi transformada usando a função logaritmo natural, interpretamos as estimativas dos parâmetros como diferenças proporcionais aproximadas.

Abaixo vemos os coeficientes arredondados para 4 casas decimais.

round(coef(m2), 4)
(Intercept)     finsqft     bedroom     lotsize 
    11.6189      0.0005      0.0429      0.0047 

Estas são proporções. Para obter porcentagens, multiplique por 100.

round(coef(m2), 4) * 100
(Intercept)     finsqft     bedroom     lotsize 
    1161.89        0.05        4.29        0.47 

Algumas interpretações básicas:

Lembre-se, a interpretação assume que todas as outras variáveis são mantidas constantes!

Uma estimativa um pouco mais precisa pode ser obtida exponencializando os coeficientes e depois interpretando os efeitos como multiplicativos em vez de aditivos. Abaixo exponenciamos usando a função exp e depois arredondamos para 4 casas decimais.

round(exp(coef(m2)), 4) 
(Intercept)     finsqft     bedroom     lotsize 
111177.8502      1.0005      1.0439      1.0047 

Por exemplo, cada quarto adicional (assumindo que todas as demais variáveis preditoras se mantém constante) aumenta o preço total esperado em cerca de 4,4%. Multiplicar por 1,0439 equivale a somar 4,39%.

Vamos revisar o resultado do resumo:

summary(m2)

Call:
lm(formula = log(totalvalue) ~ finsqft + bedroom + lotsize, data = homes)

Residuals:
     Min       1Q   Median       3Q      Max 
-2.94133 -0.14900  0.02095  0.16882  2.60752 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 1.162e+01  2.389e-02 486.272  < 2e-16 ***
finsqft     4.764e-04  7.885e-06  60.413  < 2e-16 ***
bedroom     4.292e-02  8.443e-03   5.083 3.94e-07 ***
lotsize     4.691e-03  3.220e-04  14.569  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.3336 on 3021 degrees of freedom
Multiple R-squared:  0.6904,    Adjusted R-squared:  0.6901 
F-statistic:  2246 on 3 and 3021 DF,  p-value: < 2.2e-16

VISÃO GERAL:

Todos os valores-p referem-se aos testes de hipótese de que os coeficientes são iguais a zero. Muitos estatísticos e pesquisadores preferem observar os intervalos de confiança.

round(confint(m2) * 100, 4)
                2.5 %    97.5 %
(Intercept) 1157.2037 1166.5736
finsqft        0.0461    0.0492
bedroom        2.6363    5.9473
lotsize        0.4060    0.5323

De acordo com o nosso modelo, cada quarto adicional acrescenta, em média, entre 2% e 6% ao valor de uma casa, assumindo que todas as demais variáveis preditoras se mantém constante.

Questão 4

  1. Escreva código para modelar log(totalvalue) como função de fullbath e finsqft. Chame seu modelo de m3

  2. Escreve o código para produzir os gráficos de diagnóstico dos resíduos

  3. Como interpretamos o coeficiente estimado da variáveil fullbath?

  4. Escreva código para simular os dados do modelo, em seguida, compare com o totalvalue observado. Este parece ser um bom modelo?

Preditores categóricos

Vamos adicionar hsdistrict ao modelo que acabamos de ajustar. Estar em um determinado distrito escolar afeta o valor total de uma casa?

A função table produz uma tabela de frequência (contagem) de variáveis categóricas (e também de números inteiros!):

table(homes$hsdistrict)

        Albemarle        Monticello Western Albemarle 
             1196               983               846 

Esses níveis não são números, então como R lida com isso em um modelo linear? A linguagem cria um contraste, que é uma matriz de zeros e uns. Se você tiver um fator com K níveis, terá K-1 colunas.

Neste caso teremos duas colunas: uma para Monticello HS e outra para Western Albemarle HS. Por padrão, R pega qualquer nível que venha primeiro em ordem alfabética e o torna o nível baseline ou referência.

Vejamos o contraste padrão, chamado treatment contrast. Para fazer isso, convertemos a variável/coluna hsdistrict em un fator e então usamos a função contrasts().

OBS: Não precisamos fazer isso para adicionar hsdistrict ao nosso modelo! Estamos azendo isso apenas para gerar a “definição” de contraste.

contrasts(factor(homes$hsdistrict))
                  Monticello Western Albemarle
Albemarle                  0                 0
Monticello                 1                 0
Western Albemarle          0                 1

Um modelo com hsdistrict terá dois coeficientes: Monticello e Western Albemarle

Vamos ajustar nosso novo modelo.

m4 <- lm(log(totalvalue) ~ fullbath + finsqft + hsdistrict, data = homes)
summary(m4)

Call:
lm(formula = log(totalvalue) ~ fullbath + finsqft + hsdistrict, 
    data = homes)

Residuals:
     Min       1Q   Median       3Q      Max 
-2.81786 -0.15390  0.00654  0.15716  2.75110 

Coefficients:
                              Estimate Std. Error t value Pr(>|t|)    
(Intercept)                  1.158e+01  1.731e-02 669.213  < 2e-16 ***
fullbath                     1.617e-01  8.752e-03  18.479  < 2e-16 ***
finsqft                      3.911e-04  8.673e-06  45.090  < 2e-16 ***
hsdistrictMonticello        -6.748e-02  1.391e-02  -4.849 1.30e-06 ***
hsdistrictWestern Albemarle  9.981e-02  1.472e-02   6.782 1.42e-11 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.322 on 3020 degrees of freedom
Multiple R-squared:  0.7117,    Adjusted R-squared:  0.7113 
F-statistic:  1864 on 4 and 3020 DF,  p-value: < 2.2e-16

Os coeficientes para Monticello e Western Albemarle são em relação a Albemarle HS.

round(coef(m4) * 100, 4)
                (Intercept)                    fullbath 
                  1158.1537                     16.1734 
                    finsqft        hsdistrictMonticello 
                     0.0391                     -6.7479 
hsdistrictWestern Albemarle 
                     9.9805 

Pelas estimativas, o valor de uma casa em Western Albemarle será cerca de 10% superior ao de uma casa equivalente em Albemarle. Da mesma forma, o valor de uma casa no distrito de Monticello será cerca de 7% inferior ao de uma casa equivalente no distrito de Albemarle.

Questão 4

  1. Escreva o código para modelar log(totalvalue) como função de fullbath, finsqft e cooling. Chame seu modelo de m5.

  2. Qual é a interpretação do coeficente de cooling?

Modelando Interações

Em nosso modelo acima, que incluía hsdistrict, assumimos que os efeitos eram aditivos. Por exemplo, não importava em que distrito escolar a casa ficava, o efeito de banheiro ou finsqft era o mesmo.

Os efeitos serem aditivos, também implica que o efeito de cada “banheiro completo” adicional era o mesmo, independentemente do tamanho da casa, e vice-versa. Isso pode ser muito simplista.

As interações permitem que os efeitos das variáveis dependam de outras variáveis. Novamente o conhecimento do assunto auxilia na proposição de interações. Como veremos, as interações tornam seu modelo mais flexível, mas mais difícil de entender.

R simplifica a inclusão de interações em modelos. Basta indicar uma interação entre duas variáveis colocando dois pontos (:) entre elas. Abaixo incluímos interações bidirecionais. (Você pode ter interações de três vias e superiores, mas elas são muito difíceis de interpretar.)

m6 <- lm(log(totalvalue) ~ fullbath + finsqft + hsdistrict + 
           fullbath:finsqft + fullbath:hsdistrict + 
           finsqft:hsdistrict, data = homes)
summary(m6)

Call:
lm(formula = log(totalvalue) ~ fullbath + finsqft + hsdistrict + 
    fullbath:finsqft + fullbath:hsdistrict + finsqft:hsdistrict, 
    data = homes)

Residuals:
    Min      1Q  Median      3Q     Max 
-2.7519 -0.1560 -0.0088  0.1461  2.8508 

Coefficients:
                                       Estimate Std. Error t value
(Intercept)                           1.135e+01  3.640e-02 311.961
fullbath                              2.258e-01  1.623e-02  13.907
finsqft                               5.885e-04  1.896e-05  31.036
hsdistrictMonticello                 -2.800e-01  3.820e-02  -7.330
hsdistrictWestern Albemarle          -1.186e-01  4.074e-02  -2.911
fullbath:finsqft                     -6.289e-05  4.308e-06 -14.600
fullbath:hsdistrictMonticello         1.167e-01  2.034e-02   5.739
fullbath:hsdistrictWestern Albemarle  1.190e-01  2.092e-02   5.689
finsqft:hsdistrictMonticello         -1.614e-05  2.075e-05  -0.777
finsqft:hsdistrictWestern Albemarle  -2.112e-05  2.036e-05  -1.037
                                     Pr(>|t|)    
(Intercept)                           < 2e-16 ***
fullbath                              < 2e-16 ***
finsqft                               < 2e-16 ***
hsdistrictMonticello                 2.94e-13 ***
hsdistrictWestern Albemarle           0.00363 ** 
fullbath:finsqft                      < 2e-16 ***
fullbath:hsdistrictMonticello        1.05e-08 ***
fullbath:hsdistrictWestern Albemarle 1.40e-08 ***
finsqft:hsdistrictMonticello          0.43693    
finsqft:hsdistrictWestern Albemarle   0.29967    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.3099 on 3015 degrees of freedom
Multiple R-squared:  0.7334,    Adjusted R-squared:  0.7326 
F-statistic: 921.8 on 9 and 3015 DF,  p-value: < 2.2e-16

A interpretação é muito mais difícil. Não podemos interpretar diretamente os efeitos principais de fullbath, finsqft ou hsdistrict. Eles interagem. Qual é o efeito de finsqft? Ele depende de fullbath e hsdistrict.

As interações são “significativas” ou necessárias? Podemos usar a função anova para avaliar esta questão. Essa função executa uma série de testes F parciais. Cada linha abaixo é um teste de hipótese.

A hipótese nula é que o modelo com este preditor é igual ao modelo sem o preditor. Os testes anova abaixo usam o que é chamado de somas de quadrados do Tipo I. Isso respeita a ordem das variáveis no modelo. Assim:

Se a hipótese nula for verdadeira, o valor F deve estar próximo de 1.

anova(m6)
Analysis of Variance Table

Response: log(totalvalue)
                      Df Sum Sq Mean Sq  F value    Pr(>F)    
fullbath               1 532.64  532.64 5546.784 < 2.2e-16 ***
finsqft                1 227.81  227.81 2372.402 < 2.2e-16 ***
hsdistrict             2  12.54    6.27   65.275 < 2.2e-16 ***
fullbath:finsqft       1  17.76   17.76  184.952 < 2.2e-16 ***
fullbath:hsdistrict    2   5.75    2.88   29.950  1.32e-13 ***
finsqft:hsdistrict     2   0.11    0.06    0.586    0.5566    
Residuals           3015 289.52    0.10                       
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

A interação finsqft:hsdistrict não parece contribuir muito para o modelo.

O fato de uma interação ser significativa não implica necessariamente que seja relvante ou que valha a pena incluí-la no modelo. Não podemos inferir nada sobre a natureza da interação a partir da tabela da Análise da Variância (ANOVA).

Gráficos de efeitos podem nos ajudar a visualizar e dar sentido aos modelos com interações. Vamos fazer um usando o pacote ggeffects e anlisar o que ele está exibindo.

library(ggeffects)
plot(ggpredict(m6, terms = c("fullbath", "hsdistrict")))
Model has log-transformed response. Back-transforming predictions to
  original response scale. Standard errors are still on the log-scale.

# coloca fullbath no eixo x, agrupa por hsdistrict

Qual é o efeito de fullbath? Depende. É mais forte em Western Albemarle e Monticello. É claro que grande parte da diferença ocorre em valores extremos de fullbath. As “faixas” ao redor das linhas representam intervalos com 95% confiança .

O que exatamente foi plotado? Podemos ver usando a funcão ggpredict sem plot:

ggpredict(m6, terms = c("fullbath", "hsdistrict"))
Model has log-transformed response. Back-transforming predictions to
  original response scale. Standard errors are still on the log-scale.
# Predicted values of totalvalue

# hsdistrict = Western Albemarle

fullbath | Predicted |               95% CI
-------------------------------------------
       0 |  2.44e+05 | [2.27e+05, 2.62e+05]
       1 |  3.02e+05 | [2.89e+05, 3.16e+05]
       3 |  4.65e+05 | [4.50e+05, 4.80e+05]
       4 |  5.77e+05 | [5.44e+05, 6.11e+05]
       5 |  7.15e+05 | [6.56e+05, 7.80e+05]
       8 |  1.36e+06 | [1.14e+06, 1.63e+06]

# hsdistrict = Monticello

fullbath | Predicted |               95% CI
-------------------------------------------
       0 |  2.09e+05 | [1.96e+05, 2.24e+05]
       1 |  2.59e+05 | [2.49e+05, 2.70e+05]
       3 |  3.97e+05 | [3.85e+05, 4.09e+05]
       4 |  4.91e+05 | [4.65e+05, 5.19e+05]
       5 |  6.08e+05 | [5.60e+05, 6.60e+05]
       8 |  1.15e+06 | [9.74e+05, 1.36e+06]

# hsdistrict = Albemarle

fullbath | Predicted |               95% CI
-------------------------------------------
       0 |  2.86e+05 | [2.68e+05, 3.06e+05]
       1 |  3.15e+05 | [3.03e+05, 3.29e+05]
       3 |  3.82e+05 | [3.73e+05, 3.92e+05]
       4 |  4.21e+05 | [4.01e+05, 4.42e+05]
       5 |  4.64e+05 | [4.31e+05, 5.00e+05]
       8 |  6.19e+05 | [5.30e+05, 7.23e+05]

Adjusted for:
* finsqft = 2057.60

Not all rows are shown in the output. Use `print(..., n = Inf)` to
  show all rows.

ggpredict usou nosso modelo para fazer previsões de totalvalue para vários valores de fullbath nos três distritos escolares, mantendo finsqft igual a 1828 (a mediana de finsqft).

Podemos especificar os valores se quisermos. Por exemplo, crie um gráfico de efeito para 1 a 5 banheiros e mantenha finsqft em 2.000:

plot(ggpredict(m6, terms = c("fullbath[1:5]", "hsdistrict"), 
          condition = c(finsqft = 2000)))
Model has log-transformed response. Back-transforming predictions to
  original response scale. Standard errors are still on the log-scale.

E quanto aos efeitos de finsqft e fullbath? Esta é uma interação de duas variáveis numéricas. A segunda variável deve servir como variável de agrupamento ao criar um gráfico de efeito. Abaixo definimos fullbath assumir valores de 2 a 5 e finsqft para assumir valores de 1000 a 4000 em passos de 500.

plot(ggpredict(m6, terms = c("finsqft[1000:4000 by=500]", "fullbath[2:5]")))
Model has log-transformed response. Back-transforming predictions to
  original response scale. Standard errors are still on the log-scale.

O efeito de finsqft parece diminuir em função do número de banheiros completos que uma casa tiver. Mas existem poucas casas grandes com 2 banheiros completos e, da mesma forma, poucas casas pequenas com 5 banheiros completos. Embora a interação seja “significativa” no modelo, é claramente uma interação muito pequena.

Questão 5

  1. Escreva código para modelar log(totalvalue) como função de fullbath, finsqft, cooling e a interação entre finsqft e cooling. Chame seu modelo de m7. A interação é importante?

  2. Visualize a interação usando a função ggpredict. Use [1000:4000 by=500] para definir o intervalo de finsqft no eixo x. Quão notável é essa interação?

Efeitos Não Lineares

Até agora assumimos que a relação entre um preditor e a resposta é linear (por exemplo, para uma mudança de 1 unidade em um preditor, a resposta muda em um valor fixo).

Essa suposição às vezes pode ser simplista e pouco realista. Felizmente, existem maneiras de ajustar efeitos não lineares em um modelo linear.

Aqui está um exemplo rápido de dados não lineares simulados: um polinômio de 2º grau.

x <- seq(from = -10, to = 10, length.out = 100)
set.seed(3)
y <- 1.2 + 2*x + 0.9*x^2 + rnorm(100, mean = 0, sd = 10)
nl_dat <- data.frame(y, x)
plot(y ~ x, nl_dat)

É evidente que um modelo linear não funcionará bem para estes dados. A relação entre x ey não é adequadamente capturada por um modelo linear.

Se quiséssemos tentar “recuperar” os coeficeintes que usamos na simulação desses dados, poderíamos ajustar um modelo polinomial usando a função poly() na sintaxe da fórmula:

## Codigo apenas efeitos ilustrativos
nlm1 <- lm(y ~ poly(x, degree = 2, raw = TRUE), data = nl_dat)

No entanto, a abordagem recomendada para ajustar efeitos não lineares é usar splines naturais em vez de polinômios. Splines naturais essencialmente nos permitem ajustar uma série de polinômios cúbicos conectados em nós localizados no intervalo de nossos dados.

A opção mais fácil é usar a função ns() do pacote splines, que vem instalado com R. ns significa “natural splines”. O segundo argumento são os graus de liberdade (df). Pode ser útil pensar em df como o número de vezes que a linha suave muda de direção.

Frank Harrell afirma em seu livro Regression Model Strategies que 3 a 5 df é quase sempre suficiente. Seu conselho básico é alocar mais df para variáveis que você considera mais importantes.

Vamos ver como funciona com nossos dados simulados.

library(splines)
nlm2 <- lm(y ~ ns(x, df = 2), data = nl_dat)
summary(nlm2)

Call:
lm(formula = y ~ ns(x, df = 2), data = nl_dat)

Residuals:
     Min       1Q   Median       3Q      Max 
-21.0764  -6.7423   0.1437   7.4876  18.2382 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)      62.746      2.420   25.92   <2e-16 ***
ns(x, df = 2)1  -77.728      5.433  -14.31   <2e-16 ***
ns(x, df = 2)2   91.189      2.959   30.82   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 8.642 on 97 degrees of freedom
Multiple R-squared:  0.9231,    Adjusted R-squared:  0.9215 
F-statistic: 582.2 on 2 and 97 DF,  p-value: < 2.2e-16

É impossível interpretar o resultado da função summary(). Assim, vamos visualizar o ajuste com um gráfico de efeito.

library(ggeffects)
plot(ggpredict(nlm2, terms = "x"), add.data = TRUE)
Data points may overlap. Use the `jitter` argument to add some
  amount of random variation to the location of data points and avoid
  overplotting.

Vamos voltar aos dados das casas e ajustar um efeito não linear para finsqft usando um spline natural com df igual a 5. Abaixo, incluímos também hsdistrict e lotsize e permitimos que finsqft e hsdistrict interajam.

nlm3 <- lm(
  log(totalvalue) ~ ns(finsqft, 5) + hsdistrict + lotsize +
    ns(finsqft, 5):hsdistrict,
  data = homes
)

A função anova permite avaliar o efeito não linear e a interação. Algum tipo de interação entre finsqft e hsdistrict parece estar presente.

anova(nlm3)
Analysis of Variance Table

Response: log(totalvalue)
                            Df Sum Sq Mean Sq   F value    Pr(>F)    
ns(finsqft, 5)               5 749.09 149.819 1548.1056 < 2.2e-16 ***
hsdistrict                   2  12.56   6.280   64.8896 < 2.2e-16 ***
lotsize                      1  28.35  28.354  292.9897 < 2.2e-16 ***
ns(finsqft, 5):hsdistrict   10   5.21   0.521    5.3875 6.092e-08 ***
Residuals                 3006 290.91   0.097                        
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Novamente, é impossível analisar os resultados retornados pela função summary.

summary(nlm3)

Call:
lm(formula = log(totalvalue) ~ ns(finsqft, 5) + hsdistrict + 
    lotsize + ns(finsqft, 5):hsdistrict, data = homes)

Residuals:
    Min      1Q  Median      3Q     Max 
-3.2767 -0.1459  0.0064  0.1591  2.6353 

Coefficients:
                                              Estimate Std. Error t value
(Intercept)                                 11.5109098  0.2607313  44.149
ns(finsqft, 5)1                              1.0921758  0.2464975   4.431
ns(finsqft, 5)2                              1.3571870  0.2692905   5.040
ns(finsqft, 5)3                              1.7872400  0.1520974  11.751
ns(finsqft, 5)4                              3.4685083  0.5580154   6.216
ns(finsqft, 5)5                              3.1883957  0.3163353  10.079
hsdistrictMonticello                        -0.8587210  0.3167315  -2.711
hsdistrictWestern Albemarle                 -0.1411991  0.3047520  -0.463
lotsize                                      0.0051240  0.0003031  16.908
ns(finsqft, 5)1:hsdistrictMonticello         0.8620728  0.2980655   2.892
ns(finsqft, 5)2:hsdistrictMonticello         0.7218438  0.3288128   2.195
ns(finsqft, 5)3:hsdistrictMonticello         0.8443943  0.1891643   4.464
ns(finsqft, 5)4:hsdistrictMonticello         1.2923992  0.6818569   1.895
ns(finsqft, 5)5:hsdistrictMonticello        -0.0699139  0.3723492  -0.188
ns(finsqft, 5)1:hsdistrictWestern Albemarle  0.2011020  0.2879070   0.698
ns(finsqft, 5)2:hsdistrictWestern Albemarle  0.2743639  0.3155112   0.870
ns(finsqft, 5)3:hsdistrictWestern Albemarle  0.3513783  0.1832677   1.917
ns(finsqft, 5)4:hsdistrictWestern Albemarle  0.3743132  0.6564909   0.570
ns(finsqft, 5)5:hsdistrictWestern Albemarle  0.0911532  0.3526484   0.258
                                            Pr(>|t|)    
(Intercept)                                  < 2e-16 ***
ns(finsqft, 5)1                             9.72e-06 ***
ns(finsqft, 5)2                             4.93e-07 ***
ns(finsqft, 5)3                              < 2e-16 ***
ns(finsqft, 5)4                             5.81e-10 ***
ns(finsqft, 5)5                              < 2e-16 ***
hsdistrictMonticello                         0.00674 ** 
hsdistrictWestern Albemarle                  0.64317    
lotsize                                      < 2e-16 ***
ns(finsqft, 5)1:hsdistrictMonticello         0.00385 ** 
ns(finsqft, 5)2:hsdistrictMonticello         0.02822 *  
ns(finsqft, 5)3:hsdistrictMonticello        8.35e-06 ***
ns(finsqft, 5)4:hsdistrictMonticello         0.05813 .  
ns(finsqft, 5)5:hsdistrictMonticello         0.85107    
ns(finsqft, 5)1:hsdistrictWestern Albemarle  0.48492    
ns(finsqft, 5)2:hsdistrictWestern Albemarle  0.38460    
ns(finsqft, 5)3:hsdistrictWestern Albemarle  0.05530 .  
ns(finsqft, 5)4:hsdistrictWestern Albemarle  0.56860    
ns(finsqft, 5)5:hsdistrictWestern Albemarle  0.79605    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.3111 on 3006 degrees of freedom
Multiple R-squared:  0.7322,    Adjusted R-squared:  0.7306 
F-statistic: 456.5 on 18 and 3006 DF,  p-value: < 2.2e-16

Os gráficos de efeitos são nossa única esperança para compreender este modelo. Abaixo, plotamos o valor total previsto em finqft variando de 1.000 a 3.000, agrupado por hsdistrict.

plot(ggpredict(nlm3, terms = c("finsqft[1000:3000 by=250]", "hsdistrict")))
Model has log-transformed response. Back-transforming predictions to
  original response scale. Standard errors are still on the log-scale.

O efeito de finsqft no valor total parece mais forte em Western Albemarle quando você ultrapassa 1.500 pés quadrados.

O modelo simula dados semelhantes em distribuição aos dados observados?

sim4 <- simulate(nlm3, nsim = 50)
plot(density(log(homes$totalvalue)))
for(i in 1:50)lines(density(sim4[[i]]), col = "grey80")

Ainda devemos verificar as hipóteses sobre os resíduos do modelo.

plot(nlm3)

As casas 12, 40, 963 e 1810 parecem se destacar. Vamos dar uma olhada.

h <- c(12, 40, 963, 1810)
homes[h,c("totalvalue", "finsqft", "lotsize")]

As casas 12 e 40 têm valor total muito baixo e o modelo superestima seus valores. A casa 963 tem um valor total enorme com 0 acres de tamanho de lote. A casa 1810 ocupa 611 acres e esse valor tem uma grande influência em seu valor ajustado.

cbind(observed = homes$totalvalue[h], fitted = exp(fitted(nlm3)[h]))
     observed    fitted
12       9600  132267.3
40      16400  434393.2
963   3117300  223502.3
1810  2488800 5491200.9

Questão 6

  1. Escreva código para modelar log(totalvalue) como função de finsqft com um spline natural com df = 5, cooling, e a interação de cooling e finsqft (spline natural com df = 5). Chame seu modelo de nlm4.

  2. Use a função anova para verificar se a interação parece necessária. O que você acha?

  3. Crie um gráfico de efeito de finsqft por resfriamento. Tente [1000:5000 by=250] para o intervalo de valores de finsqft.

Fim

Esta atividade teve como objetivo mostrar a vocês os fundamentos da modelagem linear em R. Esperamos que você tenha uma compreensão melhor de como funciona a modelagem linear.

O que fizemos hoje funciona para resultados numéricos independentes. Tínhamos uma observação por casa e a nossa resposta foi “valor total” (totalvalue), um número. Nossos modelos retornaram o valor total médio esperado, dados vários preditores. Este é um design bastante simples.

As coisas ficam mais complicadas quando você tem, digamos, respostas binárias ou múltiplas medidas sobre a mesma observação. Uma lista não exaustiva de outros tipos de modelos inclui:

Referências

Extra: Diretrizes para transformação de variáveis

Na modelagem anterior, aplicamos a transformação logaritmica em totalvalue para reduzir a assimetria da distribuição e, portanto, para ajudar a atender às hipóteses do modelo de regressão linear clássica.

Lembre-se de que, sem a transformação logarítmica, nossos resíduos eram grandes e assimétricos, o que é uma maneira elegante de dizer que nosso modelo não possuía bom ajuste aos dados. Um bom modelo deve ter resíduos relativamente pequenos com dispersão simétrica.

Uma transformação logarítmica fazia sentido por dois motivos:

  1. A variável resposta totalvalue era estritamente positiva, tinha um limite superior grande e cobria várias ordens de grandeza.

  2. as mudanças em totalvalue de acordo com os preditores foram relativas (multiplicativas) e não absolutas (aditivas), o que corresponde à escala logarítmica natural.

É importante observar que nem todas as variáveis com distribuição assimétrica precisam ser transformadas quando se trata de modelagem linear. As hipóteses distributivas são feitas em relação aos resíduos não em relação às variáveis resposta e preditores.

No entanto, pode haver momentos em que você precise investigar outras transformações além da logarítmica. Essas transformações alternativas, auase sempre assumem a forma de uma transformação de potência (ou seja, eleva-se a variável a uma potência usando um expoente).

As potências são geralmente simbolizadas pela letra grega lambda (\(\lambda\)). Como uma potência igual 0, temos a transformação logarítmica.

Digamos que a variável seja y. Uma paleta básica de possíveis transformações de poder inclui:

A função symbox() do pacote car cria uma avaliação visual de qual potência torna a distribuição razoavelmente simétrica.

Abaixo, quando a usamos em totalvalue, vemos que a transformação logarítmica (λ = 0) faz o melhor trabalho em tornar a distribuição mais simétrica.

car::symbox(homes$totalvalue)

Também podemos usar symbox() em um objeto modelo. Por exemplo, isso produz essencialmente o mesmo gráfico usando os resíduos do modelo em vez do valor total. Simplesmente canalize o modelo para symbox().

lm(totalvalue ~ finsqft + bedroom + lotsize, data = homes) |>
  car::symbox()

Uma “busca” pela “melhor” transformação de potência pode ser realizada com a função powerTransform(), também no pacote car. A prática usual é converter o resultado para a potência simples mais próxima listada acima. Por exemplo, podemos canalizar o resultado do modelo para powerTransform() e ver que a “melhor” transformação é cerca de 0,16.

lm(totalvalue ~ finsqft + bedroom + lotsize, data = homes) |>
  car::powerTransform() 
Estimated transformation parameter 
       Y1 
0.1616362 

0,16 está próximo de 0, então faz sentido prosseguir com uma transformação logarítmica. Isso simplifica muito a interpretação.

LS0tCnRpdGxlOiAiRXhlcmPDrWNpby9UdXRvcmlhbDogTW9kZWxvcyBkZSBSZWdyZXNzw6NvIExpbmVhciBlbSBSIgphdXRob3I6ICJTZXUgTm9tZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgUiBOb3RlYm9va3MgCgpFc3RlIMOpIHVtIFIgTm90ZWJvb2suIFF1YW5kbyB2b2PDqiBleGVjdXRhIG8gY8OzZGlnbyBSIG5vIG5vdGVib29rLCBvcyByZXN1bHRhZG9zIGFwYXJlY2VtIGFiYWl4byBkbyBjw7NkaWdvLgoKTyBjw7NkaWdvIFIgcHJlY2lzYSBlc3RhciBlbSAicGVkYcOnb3MiICgqY2h1bmtzKikgZW0gdW0gTm90ZWJvb2sgUi4gTG9nbyBhYmFpeG8sIAplc3TDoSB1bSBleGVtcGxvIGRlIHVtIHBlZGHDp28gZGUgY8OzZGlnbyBSLiBPIGPDs2RpZ28gcHJvZHV6IHVtYSBwYXLDoWJvbGEuCgpUZW50ZSBleGVjdXRhciBlc3RlIGPDs2dkaWdvLCBjb2xvY2FuZG8gbyBjdXJzb3IgZGVudHJvIGRvIGPDs2RpZ28gZSBjbGljYW5kbyBubyAKYm90w6NvICpSdW4qLCBvdSBjb2xvY2FuZG8gbyBjdXJzb3IgZGVudHJvIGRvIGPDs2RpZ28gZSBwcmVzc2lvbmFuZG8gKkN0cmwrU2hpZnQrRW50ZXIqIChXaW5kb3dzL0xpbnV4KS4KCmBgYHtyfQp4IDwtIHNlcSgtMSwgMSwgYnkgPSAwLjAxKQp5IDwtIHheMgpwbG90KHgsIHksIHR5cGUgPSAibCIpCmBgYAoKUGFyYSBvY3VsdGFyIGEgc2HDrWRhLCBjbGlxdWUgbmFzIHNldGFzIHBhcmEgZXhwYW5kaXIvcmVjb2xoZXIgYSBzYcOtZGEuIFBhcmEgCmxpbXBhciBvcyByZXN1bHRhZG9zIChvdSB1bSBlcnJvKSwgY2xpcXVlIG5vIGJvdMOjbyAieCIuCgpWb2PDqiB0YW1iw6ltIHBvZGUgcHJlc3Npb25hciAqQ3RybCtFbnRlciogKFdpbi9MaW51eCkgcGFyYSBleGVjdXRhciB1bWEgbGluaGEgCmRlIGPDs2RpZ28gcG9yIHZleiAoZW0gdmV6IGRlIHRvZG8gbyBibG9jbykuCgpBZGljaW9uZSB1bSBub3ZvIHBlZGHDp28gZGUgY8OzZGlnbyBSIGNsaWNhbmRvIG5vIGJvdMOjbyAqSW5zZXJ0IG5ldyBjb2RlIGNodW5rKiAKbmEgYmFycmEgZGUgZmVycmFtZW50YXMgb3UgcHJlc3Npb25hbmRvICpDdHJsK0FsdCtJKiAoV2luL0xpbnV4KS4KCgoKIyMgUXVlc3TDo28gMQoKSW5zaXJhIHVtIG5vdm8gcGVkYcOnbyBkZSBjw7NkaWdvIFIgYWJhaXhvLCBkaWdpdGUgZSBleGVjdXRlIApvIGPDs2RpZ286IFN5cy50aW1lKCkKCgojIyBNb2RlbG9zIGRlIFJlZ3Jlc3PDo28gTGluZWFyIGNvbSBEYWRvcyBTaW11bGFkb3MKCkVtIHZleiBkZSB1c2FyIHRlb3JpYSBlIGbDs3JtdWxhcywgdmFtb3MgZXhwbG9yYXIgZSByZXZpc2FyIGEgbW9kZWxhb3MgZGUgCnJlZ3Jlc3PDo28gbGluZWFyIHVzYW5kbyBkYWRvcyBzaW11bGFkb3MuCgpPIGPDs2RpZ28gYWJhaXhvIGZheiBvIHNlZ3VpbnRlOiAKCjEuIGF0cmlidWkgYSB4IG9zIHZhbG9yZXMgZW50cmUgMSBlIDI1LiAKCjIuIEVtIHNlZ3VpZGEsIGdlcmFtb3MgeSBjb21vIGZ1bsOnw6NvIGRlIHggdXNhbmRvIGEgZsOzcm11bGEgMTAgKyA1KngKCjMuIGNyaWFtb3MgYSBkYXRhIGZyYW1lIGQgcGFyYSBhcm1hemVuYXIgb3MgdmFsb3JlcyBkZSB4IGUgeQoKNC4gZmF6ZW1vcyB1bSBncsOhZmljbyBkZSBkaXNwZXJzw6NvIHNpbXBsZXMgZW50cmUgeSBlIHguCgpgYGB7cn0KeCA8LSAxOjI1CnkgPC0gMTAgKyA1KnggICMgZm9ybXVsYSBmb3IgYSBsaW5lCmQgPC0gZGF0YS5mcmFtZSh4LCB5KQpwbG90KHkgfiB4LCBkYXRhID0gZCkKYGBgCgotIDEwIMOpIG8gaW50ZXJjZXB0byAoJFxiZXRhXzAkKS4KLSA1IMOpIG8gY29lZmljaWVudGUgZGUgbyBpbmNsaW5hY8OjbyAoJFxiZXRhXzEkKS4gCi0geSDDqSBjb21wbGV0YW1lbnRlIGRldGVybWluYWRvIHByIHggKCR5ID0gMTAgKyA1eCQpCgpBZ29yYSB2YW1vcyBhZGljaW9uYXIgYWxndW0gInJ1w61kbyIgYW9zIG5vc3NvcyBkYWRvcyBhZGljaW9uYW5kbyB2YWxvcmVzIAoocHNldWRvKSBhbGVhdMOzcmlvcyBkZSB1bWEgZGlzdHJpYnVpw6fDo28gTm9ybWFsIGNvbSBtw6lkaWEgPSAwIGUgCmRlc3ZpbyBwYWRyw6NvID0gMTAuCgpBIGZ1bsOnw6NvIGBybm9ybSgpYCBub3MgcGVybWl0ZSBleHRyYWlyIHZhbG9yZXMgYWxlYXTDs3Jpb3MgZGUgdW1hIGRpc3RyaWJ1acOnw6NvIE5vcm1hbC4KCk8gY29tYW5kbyBgc2V0LnNlZWQoMSlgIGdhcmFudGUgcXVlIHRvZG9zIGdlcmVtb3Mgb3MgbWVzbW9zIGRhZG9zICJhbGVhdMOzcmlvcyI6CgpgYGB7cn0Kc2V0LnNlZWQoMSkKZXJybyA8LSBybm9ybShuID0gMjUsIG1lYW4gPSAwLCBzZCA9IDEwKQojIEFkaWNpb25hIGVycm8gYWxlYXRvcmlvIGEgMTAgKyA1KnggZSByZWZheiBvIGdyYWZpY28gZGUgZGlzcGVyc2FvCmQkeSA8LSAxMCArIDUqeCArIGVycm8KcGxvdCh5IH4geCwgZGF0YSA9IGQpCmBgYAoKQWdvcmEgeSBwYXJlY2UgZXN0YXIgX2Fzc29jaWFkb18gYSB4LCBtYXMgbsOjbyBjb21wbGV0YW1lbnRlIGRldGVybWluYWRvIHBvciB4LgoKeSDDqSBhIGNvbWJpbmHDp8OjbyBkZSB1bWEgcGFydGUgZml4YSBlIHVtYSBwYXJ0ZSBhbGVhdMOzcmlhOgoKMS4gcGFydGUgZml4YTogYDEwICsgNSp4YAoyLiBwYXJ0ZSBhbGVhdMOzcmlhOiBgcm5vcm0obiA9IDI1LCBtZWFuID0gMCwgc2QgPSAxMClgCgoKIyMgUXVlc3TDo28gMgoKRSBzZSByZWNlYsOqc3NlbW9zIGVzc2VzIGRhZG9zIGUgbm9zIGRpc3Nlc3NlbSBwYXJhIGRldGVybWluYXIgbyBwcm9jZXNzbyAKcXVlIG9zIGdlcm91PyBFbSBvdXRyYXMgcGFsYXZyYXMsIF90cmFiYWxoZSBkZSB0csOhcyBwYXJhIGZyZW50ZV8gZSBwcmVlbmNoYSBvcyBlc3Bhw6dvcyBlbSBicmFuY286CgoxLiBfX18gKyBfX18qeAoyLiBybm9ybShuID0gMjUsIG1lYW4gPSAwLCBzZCA9IF9fX18pCgpfRXNzYSDDqSBhIGFib3JkYWdlbSB0cmFkaWNpb25hbCBlbSBtb2RlbGFnZW0vcmVncmVzc8OjbyBsaW5lYXJfLiBWb2PDqiB0ZW0gYWxndW1hIHZhcmnDoXZlbCByZXNwb3N0YSBudW3DqXJpY2EgZSBkZXNlamEgZW5jb250cmFyIG8gbW9kZWxvIHF1ZSBnZXJvdSBvcyBkYWRvcy4KCkEgbW9kZWxhZ2VtIGRlIHJlZ3Jlc3PDo28gbGluZWFyIG3Dumx0aXBsYSB0cmFkaWNpb25hbCBhc3N1bWUgYXMgc2VndWludGVzIApoaXDDs3Rlc2VzIGNvbW8gdmVyZGFkZWlyYXMgKGVudHJlIG91dHJhcyk6CgoxLiBhIGbDs3JtdWxhIMOpIHVtYSBfc29tYSBwb25kZXJhZGFfIGRlIHByZWRpdG9yZXMgKHBvciBleGVtcGxvLCB5ID0gMTAgKyA1KngpLgoKMi4gbyBlcnJvIMOpIHVtYSByZWFsaXphw6fDo28gYWxlYXTDs3JpYSBkZSB1bWEgZGlzdHJpYnVpw6fDo28gTm9ybWFsIGNvbSAKbcOpZGlhID0gMC4KCjMuIG8gZGVzdmlvIHBhZHLDo28gZGEgZGlzdHJpYnVpw6fDo28gTm9ybWFsIMOpIGNvbnN0YW50ZSAocG9yIGV4ZW1wbG8sIDEwKQoKT3MgbW9kZWxvcyBkZSByZWdyZXNzw6NvIGxpbmVhciB0ZW50YW0gKiplc3RpbWFyKiogb3MgInBlc29zIiBkYSBwcmltZWlyYSAKaGlww7N0ZXNlIGUgbyBkZXN2aW8gcGFkcsOjbyBkYSB0ZXJjZWlyYSBoaXDDs3Rlc2UuCgpWYW1vcyBkZW1vbnN0cmFyOiBBYmFpeG8gdGVudGFtb3MgcmVjdXBlcmFyIG8gcHJvY2Vzc28gZ2VyYWRvciBwYXJhIG5vc3NvcyAKZGFkb3MuIFBhcmEgaXNzbyB1c2Ftb3MgYSBmdW7Dp8OjbyBgbG0oKWAuIFRlbW9zIHF1ZSBlc3BlY2lmaWNhciBhIGbDs3JtdWxhIHBhcmEgCmEgcHJpbWVpcmEgaGlww7N0ZXNlLiBBIHNlZ3VuZGEgZSBhIHRlcmNlaXJhIGhpcMOzdGVzZXMgZXN0w6NvIGluY29ycG9yYWRhcyAKZW0gYGxtKClgLgoKQSBmw7NybXVsYSAieSB+IHgiIHNpZ25pZmljYSBxdWUgcGVuc2Ftb3MgcXVlIG8gbW9kZWxvIMOpICJ5ID0gaW50ZXJjZXB0byArIGluY2xpbmHDp8Ojbyp4IiBvdSAoImIwICsgYjF4IikuIAoKQSBtZW5vcyBxdWUgZXNwZWNpZmlxdWVtb3MgbyBjb250csOhcmlvLCBpc3NvIHByZXNzdXDDtWUgcXVlIHF1ZXJlbW9zIGVzdGltYXIgbyBpbnRlcmNlcHRvLiBJc3NvIGRpeiDDoCBmdW7Dp8OjbyBgbG0oKWAgcGFyYSBwZWdhciBvcyBkYWRvcyBlIGVuY29udHJhciBvcyAKbWVsaG9yZXMgaW50ZXJjZXB0byBlIGNvZWZpY2llbnRlIGRlIGluY2xpbmHDp8Ojby4gX09ic2VydmUgcXVlIGVzdGUgw6kgbyBtb2RlbG8gY29ycmV0byFfCgpRdWFuZG8gdm9jw6ogdXNhIGBsbSgpYCB2b2PDqiBnZXJhbG1lbnRlIGRlc2VqYSBzYWx2YXIgb3MgcmVzdWx0YWRvcyBlbSB1bSAKb2JqZXRvLiBBYmFpeG8gc2FsdmFtb3Mgbm8gb2JqZXRvICJtb2Rfc2ltIi4gRW0gc2VndWlkYSwgdmlzdWFsaXphbW9zIG9zIApyZXN1bHRhZG9zIGRvIG1vZGVsbyB1c2FuZG8gYHN1bW1hcnkoKWA6CgpgYGB7cn0KbW9kX3NpbSA8LSBsbSh5IH4geCwgZGF0YSA9IGQpCnN1bW1hcnkobW9kX3NpbSkKYGBgCgpPIG1vZGVsbyByZXRvcm5hIGFzIHNlZ3VpbnRlcyBlc3RpbWF0aXZhczoKCjEuIHkgPSAxMS4xMzUgKyA1LjA0MiAqIHgKCjIuIGVycm8gPSBybm9ybShuID0gMjUsIG1lYW4gPSAwLCBzZCA9IDkuNykKCkVsZXMgZXN0w6NvIGJlbSBwcsOzeGltb3MgZG9zIHZhbG9yZXMgInZlcmRhZGVpcm9zIiBkZSAxMCwgNSBlIDEwIHF1ZSB1c2Ftb3MgCnBhcmEgc2ltdWxhciBvcyBkYWRvcy4gIAoKKipOYSB2aWRhIHJlYWwsIE7Dg08gQ09OSEVDRU1PUyBhIGbDs3JtdWxhIGRhIHBhcnRlIDEuIE8gdmVyZGFkZWlybyBwcm9jZXNzbyBnZXJhZG9yIGRvcyBkYWRvcyBzZXLDoSBtdWl0byBtYWlzIGNvbXBsaWNhZG8uIEEgZsOzcm11bGEgcXVlIHByb3BvbW9zIHNlcsOhIGFwZW5hcyB1bWEgYXByb3hpbWHDp8OjbyBlIHBvZGUgbsOjbyBzZXIgYm9hLioqCgoqKk5hIHZpZGEgcmVhbCwgTsODTyBTQUJFTU9TIHNlIGEgc3Vwb3Npw6fDo28gZGUgbm9ybWFsaWRhZGUgb3Ugc3Vwb3Npw6fDo28gZGUgdmFyacOibmNpYSBjb25zdGFudGUgZG8gcnXDrWRvIHPDo28gcGxhdXPDrXZlaXMuKioKCkNvbW8gcG9kZW1vcyBhdmFsaWFyIG5vc3NvIG1vZGVsbz8KClBvZGVyw61hbW9zIHVzYXIgbm9zc2FzIGVzdGltYXRpdmFzIGRvcyBwYXLDom1ldHJvcyBkbyBtb2RlbG8gcGFyYSBnZXJhciAKZGFkb3MgZSB2ZXIgc2UgZWxlcyBzZSBwYXJlY2VtIGNvbSBub3Nzb3MgZGFkb3Mgb3JpZ2luYWlzLiAKCkV4ZWN1dGUgbyBjw7NkaWdvIGFiYWl4byBkZSB1bWEgdmV6IGUgbWFpcyBkZSB1bWEgdmV6LiBPcyBwb250b3MgcHJldG9zIApuw6NvIG11ZGFtLCBtYXMgb3MgdmVybWVsaG9zIHNpbS4gSXNzbyBwYXJlY2UgbXVpdG8gYm9tISBOb3Nzb3MgZGFkb3MgCmdlcmFkb3MgcGVsbyBtb2RlbG8gcGFyZWNlbSBzZW1lbGhhbnRlcyBhb3Mgbm9zc29zIGRhZG9zIG9ic2VydmFkb3MuCgpgYGB7cn0KIyBvIG1vZGVsbyBzaW11bGFkbyBvcmlnaW5hbCDDqTogZCR5IDwtIDEwICsgNSp4ICsgZXJybwpkJHkyIDwtIDExLjEzNSArIDUuMDQyKmQkeCArIHJub3JtKDI1LCAwLCA5LjcpCnBsb3QoeSB+IHgsIGRhdGEgPSBkKSAjIGRhZG9zIG9yaWdpbmFpcyAKcG9pbnRzKGQkeCwgZCR5MiwgY29sID0gInJlZCIpICMgZGFkb3Mgc2ltdWxhZG9zCmBgYAoKVGFtYsOpbSBwb2RlbW9zIGNvbXBhcmFyIF9jdXJ2YXMgZGUgZGVuc2lkYWRlIHN1YXZlc18gZG9zIGRhZG9zIG9yaWdpbmFpcyBlIApjb20gb3MgZ2VyYWRvcyBwZWxvIG1vZGVsby4gQXMgY3VydmFzIGRlIGRlbnNpZGFkZSBzdWF2ZXMgc8OjbyBiYXNpY2FtZW50ZSAKdmVyc8O1ZXMgc3VhdmVzIGRlIGhpc3RvZ3JhbWFzLiAKClNlIHRpdmVybW9zIHVtIGJvbSBtb2RlbG8sIG9zIGRhZG9zIGdlcmFkb3MgcGVsbyBub3NzbyBtb2RlbG8gZGV2ZXLDo28gdGVyIHVtYSBkaXN0cmlidWnDp8OjbyBzZW1lbGhhbnRlIGFvcyBkYWRvcyBvcmlnaW5haXMuIEV4ZWN1dGUgbyBjw7NkaWdvIGFiYWl4byBkZSB1bWEgCnZleiBlIG1haXMgZGUgdW1hIHZlei4KCmBgYHtyfQpoaXN0KGQkeSwgZnJlcSA9IEZBTFNFKSAjIGZyZXEgPSBGQUxTRSAtPiBhcmVhIGRhcyBiYXJyYXMgc29tYW0gMQpsaW5lcyhkZW5zaXR5KGQkeSkpICAjIGRhZG9zIG9yaWdpbmFpcwpkJHkyIDwtIDExLjEzNSArIDUuMDQyKmQkeCArIHJub3JtKDI1LCAwLCA5LjcpCmxpbmVzKGRlbnNpdHkoZCR5MiksIGNvbCA9ICJyZWQiKSAgIyBkYWRvcyBzaW11bGFkb3MKYGBgCgpPIHJlc3VsdGFkbyBwYXJlY2UgYm9tLiBBIGRpc3RyaWJ1acOnw6NvIGRvcyBkYWRvcyBnZXJhZG9zIHBlbG8gbm9zc28gbW9kZWxvIMOpIAptdWl0byBzZW1lbGhhbnRlIGFvcyBkYWRvcyBvYnNlcnZhZG9zLiBWb2PDqiBkZXZlIGZhemVyIGlzc28gbWFpcyBkZSB1bWEgdmV6LCAKZGlnYW1vcyA1MCB2ZXplcywgcGFyYSBnYXJhbnRpciBxdWUgbyBtb2RlbG8gZ2VyZSBjb25zaXN0ZW50ZW1lbnRlIGRhZG9zIApzZW1lbGhhbnRlcyBhb3Mgb2JzZXJ2YWRvcy4gTW9zdHJhcmVtb3MgdW1hIG1hbmVpcmEgbWFpcyBlZmljaWVudGUgZGUgZmF6ZXIgCmVzc2Egc2ltdWxhw6fDo28gbWFpcyBhZGlhbnRlLgoKQ29tbyBhY2hhbW9zIHF1ZSBub3NzbyBtb2RlbG8gw6kg4oCcYm9t4oCdLCBwb2RlbW9zIHVzw6EtbG8gcGFyYSBmYXplciB1bWEgcHJldmlzw6NvLiAKUG9yIGV4ZW1wbG8sIHF1YW5kbyB4ID0gMTAgcXVhbCDDqSBvIHZhbG9yIGVzcGVyYWRvIGRlIHk/IERpdG8gZGUgb3V0cmEgZm9ybWEsIApxdWFsIMOpIGEgX23DqWRpYV8gZGUgeSBjb25kaWNpb25hbCBhIHggPSAxMD8gCgpQb2RlbW9zIG9idGVyIGVzc2EgcHJldmlzw6NvIGNvbSBhIGZ1bsOnw6NvIGBwcmVkaWN0KClgLiBPIGFyZ3VtZW50byAKYGludGVydmFsID0gImNvbmZpZGVuY2UiYCBzaWduaWZpZmNhIHF1ZSBkZXNlamFtb3MgdW1hIGVzdGltYXRpdmEgcG9yIAppbnRlcnZhbG8gY29tIDk1JSBkZSBjb25maWFuw6dhIChJQykgcGFyYSBlc3RhIG3DqWRpYSBjb25kaWNpb25hbC4KCmBgYHtyfQpwcmVkaWN0KG1vZF9zaW0sIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHggPSAxMCksIGludGVydmFsID0gImNvbmZpZGVuY2UiKQpgYGAKCkEgbcOpZGlhIGVzcGVyYWRhIChjb25kaWNpb25hbCkgZGUgeSBxdWFuZG8geCA9IDEwIMOpIGRlIGNlcmNhIGRlIDYxLDYgY29tIHVtIApJQyBkZSA5NSUgZGUgKDU3LDIsIDY1LDkpLiBPIElDIG5vcyBkw6EgdW1hIG5vw6fDo28gZGUgcXXDo28gaW5jZXJ0YSDDqSBlc3NhIG3DqWRpYSBlc3BlcmFkYS4gTmEgdmVyZGFkZSwgc2VyaWEgbWVsaG9yIHJlbGF0YXIgaXNzbyBjb21vIOKAnGEgbcOpZGlhIGVzcGVyYWRhIGRlIHkgCnF1YW5kbyB4ID0gMTAgZGV2ZSBlc3RhciBlbnRyZSA1NyBhIDY24oCdLgoKUG9kZXLDrWFtb3MgdGFtYsOpbSB0ZW50YXIgcmVzdW1pciBhIHJlbGHDp8OjbyBlbnRyZSB5IGUgeCBleGFtaW5hbmRvIG9zIApjb2VmaWNpZW50ZXMgKG91IHBlc29zKSBvYnRpZG9zIG5vIHN1bcOhcmlvIGRvcyByZXN1bHRhZG9zLiBQb2RlbW9zIApleHRyYWlyIG9zIGNvZWZpY2llbnRlcyBkbyBzdW3DoXJpbyB1c2FuZG8gYSBmdW7Dp8OjbyBgY29lZigpYDoKCmBgYHtyfQpjb2VmKHN1bW1hcnkobW9kX3NpbSkpCmBgYAoKTyBjb2VmaWNpZW50ZSB4IGRpeiBxdWUgeSBhdW1lbnRhIGNlcmNhIGRlIDUgdW5pZGFkZXMgcGFyYSBjYWRhIGF1bWVudG8gCmRlIHVtYSB1bmlkYWRlIGVtIHgsIHZhcmlhbmRvIGVtIG3DqWRpYSAwLDI3IGFjaW1hIG91IGFiYWl4byBkZSA1LiBPIGVycm8gCnBhZHLDo28gbm9zIGTDoSB1bWEgaW5kaWNhw6fDo28gZGEgaW5jZXJ0ZXphIG5lc3RhIGVzdGltYXRpdmEuIEZhbGFyZW1vcyBtYWlzIApzb2JyZSBvcyB2YWxvcmVzIHQgZSB2YWxvcmVzLXAgYWRpYW50ZS4KCioqUkVTVU1POlwqKgoKRXNzZW5jaWFsbWVudGUsIGEgbW9kZWxhZ2VtIGRlIHJlZ3Jlc3PDo28gbGluZWFyIGLDoXNpY2EgY29uc2lzdGVtIGVtOgoKMS4gcHJvcG9yIGUgYWp1c3RhciB1bSBtb2RlbG8gbGluZWFyCgoyLiBkZXRlcm1pbmFyIHNlIG8gbW9kZWxvIMOpIGJvbSBlIHNlIGFzIHByZW1pc3NhcyBzw6NvIGF0ZW5kaWRhcyBlbSBzdWEgbWFpb3JpYS4KCjMuIHVzYXIgbyBtb2RlbG8gcGFyYSBleHBsaWNhciBhIHJlbGHDp8OjbyBlbnRyZSB5IGUgeCBlL291IGZhemVyIHByZXZpc8O1ZXMuCgoKVmFtb3MgdmVyIG8gcXVlIGFjb250ZWNlIHF1YW5kbyBhanVzdGFtb3MgdW0gbW9kZWxvICJydWltIi4gQWJhaXhvLCAKYWRpY2lvbmFtb3MgdW1hIG5vdmEgY29sdW5hIMOgIGRhdGEgZnJhbWUgYGRgIGNoYW1hZGEgYHpgLCBxdWUgw6kgdW1hIAphbW9zdHJhIGFsZWF0w7NyaWEgZGUgbsO6bWVyb3Mgbm8gaW50ZXJ2YWxvIGRlIC0xMDAgYSAxMDAuIGBydW5pZigpYCAKYW1vc3RyYSBuw7ptZXJvcyBkZSB1bWEgZGlzdHJpYnVpw6fDo28gdW5pZm9ybWUgZW50cmUgbWluIGUgbWF4LgoKYGBge3J9CnNldC5zZWVkKDQpCmQkeiA8LSBydW5pZigyNSwgbWluID0gLTEwMCwgbWF4ID0gMTAwKQpgYGAKCkxFTUJSRVRFOiDDiSBwb3Nzw612ZWkgYWRpY2lvbmFyIHVtIG5vdm8gdHJlY2hvIGRlIGPDs2RpZ28gUiBjbGljYW5kbyAKbm8gYm90w6NvICpJbnNlcnQgbmV3IGNvZGUgY2h1bmsqIG5hIGJhcnJhIGRlIGZlcnJhbWVudGFzIG91IApwcmVzc2lvbmFuZG8gKkN0cmwrQWx0K0kqIChXaW4vTGludXgpLgoKCiMjIFF1ZXN0w6NvIDMKCjEuIE1vZGVsZSBgeWAgY29tbyB1bWEgZnVuw6fDo28gZGUgYHpgIHVzYW5kbyBgbG0oeSB+IHosIGRhdGEgPSBkKWAgZSAKc2FsdmUgb3MgcmVzdWx0YWRvcyBlbSB1bSBvYmpldG8gY2hhbWFkbyBgbW9kX3NpbTJgLCB2ZWphIG9zIHJlc3VsdGFkb3MgCnVzYW5kbyBhIGZ1bsOnw6NvIGBzdW1tYXJ5KClgLiBRdWFsIG1vZGVsbyBmb2kgZXN0aW1hZG8/IFF1YWwgw6kgbyBkZXN2aW8gCnBhZHLDo28gZXN0aW1hZG8gZG8gZXJybyBhbGVhdMOzcmlvIG5vcm1hbG1lbnRlIGRpc3RyaWJ1w61kbz8KCjIuIFVzZSBvIG1vZGVsbyBwYXJhIHNpbXVsYXIgdW0gaGlzdG9ncmFtYXMgb3UgZGVuc2lkYWRlIGUgY29tcGFyZSBjb20gbyAKaGlzdG9ncmFtYSBlIGRlbnNpZGFkZSBvcmlnaW5hbCBkZSBgZCR5YC4gRXhlY3V0ZSBvIGPDs2RpZ28gdsOhcmlhcyB2ZXplcyAKcGFyYSB2ZXIgY29tbyBhIGN1cnZhIGRlIGRlbnNpZGFkZSBnZXJhZGEgcGVsbyBtb2RlbG8gdmFyaWEuCgpBZ29yYSwgdmFtb3MgYXBsaWNhciB1bSBtb2RlbG8gZGUgcmVncmVzc8OjbyBsaW5lYXIgYSBkYWRvcyByZWFpcy4KCgojIyBJbXBvcnRhbmRvIG9zIERhZG9zCgpWYW1vcyBpbXBvcnRhciBvcyBkYWRvcyBxdWUgdXNhcmVtb3MgLiBPcyBkYWRvcyBjb20gb3MgcXVhaXMgdHJhYmFsaGFyZW1vcyAKc8OjbyBkYWRvcyBpbW9iaWxpw6FyaW9zIGRvIGNvbmRhZG8gZGUgQWxiZW1hcmxlIG5vIGVzdGFkbyBkYSBWaXJnw61uaWEvRVVBLCAKYmFpeGFkb3MgZG8gT2ZmaWNlIG9mIEdlb2dyYXBoaWMgRGF0YSBTZXJ2aWNlcy4gVXNhcmVtb3MgdW1hIApfYW1vc3RyYSBhbGVhdMOzcmlhXyBkb3MgZGFkb3MuCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KHJlYWRyKQp1cmwgPC0gJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9jbGF5Zm9yZC9kYXRhdml6X3dpdGhfZ2dwbG90Mi9tYXN0ZXIvYWxiX2hvbWVzLmNzdicKaG9tZXMgPC0gcmVhZHI6OnJlYWRfY3N2KGZpbGUgPSB1cmwpCmRwbHlyOjpnbGltcHNlKGhvbWVzKQpgYGAKClZlamFtb3MgYXMgcHJpbWVpcmFzIDYgbGluaGFzOgoKYGBge3J9CmhlYWQoaG9tZXMpCmBgYAoKRGljaW9uw6FyaW8gZG9zIERhZG9zOgoKLSAqeWVhcmJ1aWx0KjogYW5vIGVtIHF1ZSBhIGNhc2EgZm9pIGNvbnN0cnXDrWRhLgotICpmaW5zcWZ0KjogdGFtYW5obyBkYSBjYXNhIGVtIHDDqXMgcXVhZHJhZG9zLgotICpjb29saW5nKjogJ0FyIENlbnRyYWwnIHZlcnN1cyAnU2VtIEFyIENlbnRyYWwnLgotICpiZWRyb29tKjogbnVtZXJvIGRlIHF1YXJ0b3MuCi0gKmZ1bGxiYXRoKjogbsO6bWVybyBkZSBiYW5oZWlyb3MgY29tcGxldG9zIChzYW5pdMOhcmlvLCBwaWEgZSBiYW5oZWlyYSkuCi0gKmhhbGZiYXRoKjogbsO6bWVybyBkZSBsYXZhYm9zIChzb21lbnRlIHZhc28gc2FuaXTDoXJpbyBlIHBpYSkuCi0gKmxvdHNpemUqOiB0YW1hbmhvIGRvIHRlcnJlbm8gb25kZSBhIGNhc2EgZXN0w6EgbG9jYWxpemFkYSwgZW0gaGVjLgotICp0b3RhbHZhbHVlKjogdmFsb3IgdG90YWwgYXZhbGlhZG8gZGEgY2FzYSBlIGRhIHByb3ByaWVkYWRlLgotICplc2Rpc3RyaWN0KjogZXNjb2xhIGluZmFudGlsIMOgIHF1YWwgYXMgY3JpYW7Dp2FzIHF1ZSB2aXZlbSBuZXNzYSDDoXJlYSBzw6NvIGRlc2lnbmFkYXMgcGFyYSBmcmVxdWVudGFyIGNvbSBiYXNlIGVtIHNldSBlbmRlcmXDp28gcmVzaWRlbmNpYWwuCi0gKm1zZGlzdHJpY3QqOiBlc2NvbGEgZnVuZGFtZW50YWwgw6AgcXVhbCBhcyBjcmlhbsOnYXMgcXVlIHZpdmVtIG5lc3NhIMOhcmVhIHPDo28gZGVzaWduYWRhcyBwYXJhIGZyZXF1ZW50YXIuCi0gKmhzZGlzdHJpY3QqOiBlc2NvbGEgZG8gZW5zaW5vIG3DqWRpbyDDoCBxdWFsIG9zIGFkb2xlc2NlbnRlcyBxdWUgdml2ZW0gbmVzc2EKw6FyZWEgc8OjbyBkZXNpZ25hZGFzIHBhcmEgZnJlcXVlbnRhci4KLSAqY2Vuc3VzdHJhY3QqOiBvIHNldG9yIGNlbnNpdMOhcmlvIGVtIHF1ZSBhIGNhc2EgZXN0w6EgbG9jYWxpemFkYS4KLSAqYWdlKjogZGEgY2FzYSBlbSBhbm9zIGEgcGFydGlyIGRlIDIwMTguCi0gKmNvbmRpdGlvbio6IGNvbmRpw6fDo28gYXZhbGlhZGEgZGEgY2FzYSAoU3Vic3RhbmRhcmQsIFBvb3IsIEZhaXIsIEF2ZXJhZ2UsIApHb29kLCBFeGNlbGxlbnQpLgotICpmcCo6IGluZGljYWRvciBzZSBhIGNhc2EgdGVtIGxhcmVpcmEgKDAgPSBubywgMSA9IHllcykuCgoKIyMgUmVncmVzc8OjbyBMaW5lYXIgY29tIERhZG9zIEltb2JpbGnDoXJpb3MKCkRpZ2Ftb3MgcXVlIHF1ZXJlbW9zIG1vZGVsYXIgbyB2YWxvciB0b3RhbCBtw6lkaW8gZGUgdW1hIGNhc2EgKHJlcHJlc2VudGFkbyAKcGVsYSB2YXJpw6F2ZWwgYHRvdGFsdmFsdWVgKSBlbSBmdW7Dp8OjbyBkZSB2w6FyaWFzIGNhcmFjdGVyw61zdGljYXMsIGNvbW8gCnRhbWFuaG8gZG8gbG90ZSwgbWV0cm9zIHF1YWRyYWRvcyBjb25zdHJ1w61kb3MsIHByZXNlbsOnYSBkZSBhciBjZW50cmFsLCBldGMuCgpWYW1vcyB2ZXIgY29tbyBhIGRpc3RyaWJ1acOnw6NvIGRhIHZhcmnDoXZlbCBxdWUgcmVwcmVzZW50YSBvIHZhbG9yIHRvdGFsIAp1c2FuZG8gdW0gaGlzdG9ncmFtYS4gT2JzZXJ2ZSBxdWUgYSBkaXN0cmlidWnDp8OjbyDDqSBiYXN0YW50ZSBhc3NpbcOpdHJpY2E6CgpgYGB7cn0KaGlzdChob21lcyR0b3RhbHZhbHVlKQpgYGAKClBvZGVtb3MgdGFtYsOpbSBjcmlhciB1bWEgY3VydmEgZGUgZGVuc2lkYWRlLCBxdWUgw6kgdW1hIHZlcnPDo28gc3Vhdml6YWRhIApkZSB1bSBoaXN0b2dyYW1hOgoKYGBge3J9CnBsb3QoZGVuc2l0eShob21lcyR0b3RhbHZhbHVlKSkKYGBgCgoKUGFyYSBtb2RlbGFyIG8gdmFsb3IgbcOpZGlvIHRvdGFsIGRhcyByZXNpZMOqbmNpYXMgZW0gZnVuw6fDo28gZGUgZGl2ZXJzYXMgY2FyYWN0ZXLDrXN0aWNhcywgcHJlY2lzYW1vcyBwcm9wb3IgdW0gbW9kZWxvIGxpbmVhci4gQW8gY29udHLDoXJpbyBkbyBleGVtcGxvIGFudGVyaW9yLCBlc3RlcyBuw6NvIHPDo28gZGFkb3Mgc2ltdWxhZG9zIHBhcmEgb3MgcXVhaXMgY29uaGVjZW1vcyBvIHByb2Nlc3NvIApnZXJhZG9yIGRvcyBkYWRvcy4gCgpDb21vIHByb3BvciB1bSBtb2RlbG8/IMOJIGZ1bmRhbWVudGFsIHRlciBhbGd1bSBjb25oZWNpbWVudG8gc29icmUgbyBmZW7DtG1lbm8KClZhbW9zIGFqdXN0YXIgdW0gbW9kZWxvIGxpbmVhciB1c2FuZG8gcMOpcyBxdWFkcmFkb3MgKGBmaW5zcWZ0YCksIG8gCm7Dum1lcm8gZGUgcXVhcnRvcyAoYGJlZHJvb21gKSBlIHRhbWFuaG8gZG8gbG90ZSAoYGxvdHNpemVgKS4gTyBzaW5hbCAKZGUgbWFpcyAoKykgc2lnbmlmaWNhIOKAnGluY2x1aXLigJ0gbm8gbW9kZWxvLgoKYGBge3J9Cm0xIDwtIGxtKHRvdGFsdmFsdWUgfiBmaW5zcWZ0ICsgYmVkcm9vbSArIGxvdHNpemUsIGRhdGEgPSBob21lcykKc3VtbWFyeShtMSkKYGBgCgpBIGZ1bsOnw6NvIGBjb2VmKClgIGV4dHJhaSBvcyBjb2VmaWNpZW50ZXMsIG91IHBhcsOibWV0cm9zIGVzdGltYWRvczoKCmBgYHtyfQpjb2VmKG0xKQpgYGAKCkNvbSBvcyBwYXLDom1ldHJvcyBlc3RpbWFkb3MsIHBvZGVtb3MgZXNjcmV2ZSBvIG1vZGVsbyBlc3RpbWFkbzoKCmBgYAp0b3RhbHByaWNlID0gLTEzMzMyOC4yNDgyICsgMjg0LjQ2MTMqZmluc3FmdCArIC0xMzIxOC40MDkxKmJlZHJvb20gKwogICAgICAgICAgICAgICA0MjY4Ljc2NTUqbG90c2l6ZWAKYGBgCgpBbGd1bWFzIGludGVycHJldGHDp8O1ZXMgYsOhc2ljYXM6CgotIGNhZGEgcMOpIHF1YWRyYWRvIGFjYWJhZG8gYWRpY2lvbmFsIGFkaWNpb25hIGNlcmNhIGRlIFVTJCAyODQgYW8gcHJlw6dvLCAKICBtYW50aWRhcyBhcyBkZW1haXMgdmFyacOhdmVpcyBjb25zdGFudGVzLgogIAotIGNhZGEgcXVhcnRvIGFkaWNpb25hbCByZWR1eiBvIHByZcOnbyBlbSBVUyQgMTMuMjE4LCBtYW50aWRhcyBhcyAKICBkZW1haXMgdmFyacOhdmVpcyBjb25zdGFudGVzLgogIAotIGNhZGEgYWNyZSBhZGljaW9uYWwgZGUgdGFtYW5obyBkZSBsb3RlIGFkaWNpb25hIGNlcmNhIGRlIFVTJCA0LjI2OCBhbyAgIAogIHByZcOnbywgbWFudGlkYXMgYXMgZGVtYWlzIHZhcmnDoXZlaXMgY29uc3RhbnRlcy4KCkNhZGEgdW1hIGRlc3NhcyBpbnRlcnByZXRhw6fDtWVzIGFzc3VtZSBxdWUgX3RvZGFzIGFzIG91dHJhcyB2YXJpw6F2ZWlzIHPDo28gbWFudGlkYXMgY29uc3RhbnRlc18hIFBvcnRhbnRvLCBlc3RpbWEtc2UgcXVlIGFkaWNpb25hciB1bSBxdWFydG8gYSB1bWEgY2FzYSwgc2VtIGF1bWVudGFyIG8gdGFtYW5obyBkbyBsb3RlIG91IG9zIG1ldHJvcyBxdWFkcmFkb3MgYWNhYmFkb3MgZGEgY2FzYSwgcmVkdXphIG8gdmFsb3IgZGEgY2FzYS4gSXNzbyBmYXogc2VudGlkbz8KCkVzdGUgw6kgdW0gbW9kZWxvIOKAnGJvbeKAnT8gVmFtb3Mgc2ltdWxhciBvcyBkYWRvcyBkbyBtb2RlbG8gZSBjb21wYXLDoS1sb3MgY29tIG9zIGRhZG9zIG9ic2VydmFkb3MuIFVtIOKAnGJvbeKAnSBtb2RlbG8gZGV2ZSBnZXJhciBkYWRvcyBzZW1lbGhhbnRlcyBhb3MgZGFkb3Mgb3JpZ2luYWlzLgoKUG9kZXLDrWFtb3MgZmF6ZXIgaXNzbyBtYW51YWxtZW50ZToKCmBgYHtyfQpzaW1feSA8LSAtMTMzMzI4LjI0ODIgKyAyODQuNDYxMypob21lcyRmaW5zcWZ0ICsgCiAgLTEzMjE4LjQwOTEqaG9tZXMkYmVkcm9vbSArIDQyNjguNzY1NSpob21lcyRsb3RzaXplICsgCiAgcm5vcm0oMzAyNSwgc2QgPSAyMjcyMDApCmBgYAoKClVtYSBtYW5laXJhIG1haXMgZsOhY2lsIGUgcsOhcGlkYSDDqSB1c2FyIGEgZnVuw6fDo28gYHNpbXVsYXRlKClgIHF1ZSBwZXJtaXRlIHNpbXVsYXIgbcO6bHRpcGxhcyBhbW9zdHJhcy4gQXF1aSBnZXJhbW9zIDUwIGFtb3N0cmFzLiBDYWRhIGFtb3N0cmEgdGVyw6EgbyBtZXNtbyBuw7ptZXJvIGRlIG9ic2VydmHDp8O1ZXMgcXVlIG5vc3NhIGFtb3N0cmEgb3JpZ2luYWwgKG4gPSAzLjAyNSkuIENhZGEgdmFsb3IgZGUgYW1vc3RyYSDDqSBnZXJhZG8gdXNhbmRvIG5vc3NvcyB2YWxvcmVzIG9ic2VydmFkb3MgcGFyYSBgZmluc3FmdGAsIGBiZWRyb29tYCBlIGBsb3RzaXplYC4gTyByZXN1bHRhZG8gw6kgdW1hIGRhdGEgZnJhbWUgY29tIDUwIGNvbHVuYXMuCgpgYGB7cn0Kc2ltMSA8LSBzaW11bGF0ZShtMSwgbnNpbSA9IDUwKQpgYGAKCgpBZ29yYSB2YW1vcyByZXByZXNlbnRhciBncmFmaWNhbWVudGUgb3MgZGFkb3Mgc2ltdWxhZG9zIGUgb3MgZGFkb3Mgb2JzZXJ2YWRvcyB1c2FuZG8gZ3LDoWZpY29zIGRlIGRlbnNpZGFkZS4gVXNhbW9zIHVtIGxvb3AgYGZvcmAgcGFyYSBhZGljaW9uYXIgZXN0aW1hdGl2YXMgZGUgZGVuc2lkYWRlIHN1YXZlcyBkYXMgNTAgc2ltdWxhw6fDtWVzLiAKCk8gY29tYW5kbyBgc2ltMVtbaV1dYCBleHRyYWkgYSBjb2x1bmEgX2lfIGNvbW8gdW0gdmV0b3IuIChleGVjdXRlIG8gCmPDs2RpZ28gZGUgdW1hIHZlei4pCgpgYGB7cn0KcGxvdChkZW5zaXR5KGhvbWVzJHRvdGFsdmFsdWUpKQpmb3IoaSBpbiAxOjUwKWxpbmVzKGRlbnNpdHkoc2ltMVtbaV1dKSwgY29sID0gImdyZXk4MCIpCmBgYAoKKFZlamEgbyBmaW5hbCBkbyBub3RlYm9vayBwYXJhIHNhYmVyIGNvbW8gY3JpYXIgZXNzZSBncsOhZmljbyB1c2FuZG8gCm8gcGFjb3RlIGdncGxvdDIgZSBjb21vIHRyYW5zZm9ybWFyIGVzc2UgY8OzZGlnbyBlbSB1bWEgZnVuw6fDo28uKQoKRXN0ZSBuw6NvIHBhcmVjZSBzZXIgdW0gYm9tIG1vZGVsby4gTmEgdmVyZGFkZSBhbGd1bnMgZG9zIG5vc3NvcyB2YWxvcmVzCnNpbXVsYWRvcyBzw6NvIG5lZ2F0aXZvcyEKCkFudGVzIGRlIHJldmlzYXJtb3MgbyBtb2RlbG8sIGxlbWJyZS1zZSBkYXMgcHJpbmNpcGFpcyBoaXDDs3Rlc2VzOgoKMSkgYHRvdGFsdmFsdWVgIHBvZGUgc2VyIG1vZGVsYWRvIHBvciB1bWEgc29tYSBwb25kZXJhZGE6CiAgICB2YWxvciB0b3RhbCA9IGludGVyY2VwdGHDp8OjbyArIHDDqXMgcXVhZHJhZG9zICsgcXVhcnRvcyArIHRhbWFuaG8gZG8gbG90ZQoyKSAgTyBlcnJvIGFsZWF0w7NyaSBzZWd1ZSBhcHJveGltYWRhbWVudGUgdW1hIGRpc3RyaWJ1acOnw6NvIE5vcm1hbCAKICAgIGNvbSBtw6lkaWEgMC4KMykgIE8gZGVzdmlvLXBhZHLDo28gKHZhcmnDom5jaWEpIGRlc3RhIGRpc3RyaWJ1acOnw6NvIE5vcm1hbCDDqSBjb25zdGFudGUKCkEgbGluZ3VhZ2VtIFIgZm9ybmVjZSBhbGd1bnMgZ3LDoWZpY29zIGRlIGRpYWduw7NzdGljbyBiw6FzaWNvcyBwYXJhIAphdmFsaWFyIGFzIGhpcMOzdGVzZXMgMiBlIDMgc29icmUgb3MgcmVzw61kdW9zLiBCYXN0YSBhcGxpY2FyIGEgCmZ1bsOnw6NvIGBwbG90YCBubyBvYmpldG8gcXVlIGFybWF6ZW5vdSBvcyByZXN1bHRhZG9zIGRhIGVzdGltYcOnw6NvIApkbyBtb2RlbG8uCgpgYGB7cn0KcGxvdChtMSkKYGBgCgpDb21vIGludGVycHJldGFyIG9zIGdyw6FmaWNvczoKCjEuIFJlc8OtZHVvcyB2cyBWYWxvcmVzIEFqdXN0YWRvczogZGV2ZSBwb3NzdWlyIHVtYSBsaW5oYSBob3Jpem9udGFsIGNvbSAKZGlzcGVyc8OjbyB1bmlmb3JtZSBlIHNpbcOpdHJpY2EgZG9zIHBvbnRvczsgc2UgbsOjbywgaGF2ZXLDoSBldmlkw6puY2lhIGRlIApxdWUgYSB2YXJpw6JuY2lhIG91IGRlc3Zpby1wYWRyw6NvIG7Do28gw6kgY29uc3RhbnRlLgoKMi4gUVEgbm9ybWFsOiBvcyBwb250b3MgZGV2ZW0gZmljYXIgcHLDs3hpbW9zIMOgIGxpbmhhIGRpYWdvbmFsOyBjYXNvIGNvbnRyw6FyaW8sIGhhdmVyw6EgZXZpZMOqbmNpYSBkZSBxdWUgbyByZXPDrWR1byBuw6NvIHNlZ3VlLCBhcHJveGltYWRhbWVudGUsIAp1bWEgZGlzdHJpYnVpw6fDo28gbj1Ob3JtYWwuCgozLiBTY2FsZS1Mb2NhdGlvbjogZGV2ZSB0ZXIgdW1hIGxpbmhhIGhvcml6b250YWwgY29tIGRpc3BlcnPDo28gdW5pZm9ybWUgZGUgCnBvbnRvczsgKHNlbWVsaGFudGUgYW8gbsK6IDEsIG1hcyBtYWlzIGbDoWNpbCBkZSBkZXRlY3RhciB0ZW5kw6puY2lhIG5hIGRpc3BlcnPDo28pLgoKNC4gUmVzaWR1YWlzIHZzIEFsYXZhbmNhZ2VtOiBwb250b3MgZm9yYSBkYXMgY3VydmFzIGRlIG7DrXZlbCBzw6NvIG9ic2VydmHDp8O1ZXMgaW5mbHVlbnRlcy4gQWxhdmFuY2FnZW0gw6kgYSBkaXN0w6JuY2lhIGRvIGNlbnRybyBkZSB0b2RvcyBvcyBwcmVkaXRvcmVzLiBVbWEgb2JzZXJ2YcOnw6NvIGNvbSBhbHRhIGFsYXZhbmNhZ2VtIHRlbSBpbmZsdcOqbmNpYSBzdWJzdGFuY2lhbCBubyB2YWxvciBhanVzdGFkby4KClBvciBwYWRyw6NvLCBvcyAzIHBvbnRvcyAibWFpcyBleHRyZW1vcyIgc8OjbyByb3R1bGFkb3MgcGVsbyBuw7ptZXJvIGRhIGxpbmhhLiAKMjY1OCBhcGFyZWNlIGVtIHRvZG9zIGFzIHF1YXRybyBncsOhZmljb3MuIMOJIHVtYSBjYXNhIG11aXRvIGdyYW5kZSBlIGNhcmEuCgpgYGB7cn0KaG9tZXNbMjY1OCxdCmBgYAoKRXNzZXMgZ3LDoWZpY29zIHJldmVsYW0gcXVlIG5vc3NhcyBzdXBvc2nDp8O1ZXMgc29icmUgYSBub3JtYWxpZGFkZSAKZSB2YXJpw6JuY2lhIGNvbnN0YW50ZSBkb3MgcmVzw61kdW9zIHPDo28gYWx0YW1lbnRlIHN1c3BlaXRhcy4gCk5vc3NvIG1vZGVsbyDDqSBzaW1wbGVzbWVudGUgcnVpbS4KCk8gcXVlIHBvZGVtb3MgZmF6ZXI/CgpBIHZhcmlhw6fDo28gbsOjbyBjb25zdGFudGUgcG9kZSBzZXIgZXZpZMOqbmNpYSBkZSB1bSBtb2RlbG8gZXJyYWRvIG91IGRlIHVtYSAKdmFyacOhdmVsIHJlc3Bvc3RhIG11aXRvIGFzc2ltw6l0cmljYSAob3UgdW0gcG91Y28gZGUgYW1ib3MpLiBMZW1icmUtc2UgZGUgCnF1ZSBhIHZhcmnDoXZlbCByZXNwb3N0YSDDqSBiYXN0YW50ZSBhc3NpbcOpdHJpY2E6CgpgYGB7cn0KaGlzdChob21lcyR0b3RhbHZhbHVlKQpgYGAKCgpBbyBsaWRhciBjb20gdW1hIHZhcmnDoXZlbCByZXNwb3N0YSBlc3RyaXRhbWVudGUgcG9zaXRpdmEgZSBtdWl0byBhc3NpbcOpdHJpY2EgQW8gbGlkYXIgY29tIHVtYSByZXNwb3N0YSBlc3RyaXRhbWVudGUgcG9zaXRpdmEgZSBtdWl0byBkaXN0b3JjaWRhIChjb21vIHZhcmnDoXZlaXMgCnF1ZSByZXByZXNlbnRhbSBwcmXDp29zKSwgw6kgY29tdW0gdHJhbnNmb3JtYXIgYSB2YXJpw6F2ZWwgcmVzcG9zdGEgcGFyYSB1bWEgCmVzY2FsYSBkaWZlcmVudGUuIAoKVW1hIHRyYW5zZm9ybWHDp8OjbyBjb211bSDDqSB1bWEgdHJhbnNmb3JtYcOnw6NvIGxvZ2Fyw610bWljYS4gUXVhbmRvIGFwbGljYW1vcyBvIGxvZ2FyaXRtbyBuYXR1cmFsIGEgdmFyacOhdmVsIGB0b3RhbHZhbHVlYCwgYSBkaXN0cmlidWnDp8OjbyBwYXJlY2UgdW0gcG91Y28gbWFpcyBzaW3DqXRyaWNhLCBlbWJvcmEgc2VqYSBpbXBvcnRhbnRlIG9ic2VydmFyIHF1ZSBpc3NvIG7Do28gw6kgdW1hIHN1cG9zacOnw6NvIGRhIAptb2RlbGFnZW0gbGluZWFyIQoKYGBge3J9Cmhpc3QobG9nKGhvbWVzJHRvdGFsdmFsdWUpKQpgYGAKCgpWYW1vcyB0ZW50YXIgbW9kZWxhciBhIHZhcmnDoXZlbCBgdG90YWx2YWx1ZWAgbG9nLXRyYW5zZm9ybWFkYS4KCmBgYHtyfQptMiA8LSBsbShsb2codG90YWx2YWx1ZSkgfiBmaW5zcWZ0ICsgYmVkcm9vbSArIGxvdHNpemUsIGRhdGEgPSBob21lcykKYGBgCgpPcyBncsOhZmljb3MgZGUgZGlhZ27Ds3N0aWNvIGRvcyByZXPDs2R1b3MgcGFyZWNlbSBtZWxob3Jlcy4KCmBgYHtyfQpwbG90KG0yKQpgYGAKCgpNYXMgZXN0ZSDDqSB1bSDigJxib20gbW9kZWxv4oCdPyBOb3NzbyBtb2RlbG8gcHJvcG9zdG8gZGUgc29tYXMgcG9uZGVyYWRhcyDDqSBib20/IE5vdmFtZW50ZSwgdmFtb3Mgc2ltdWxhciBkYWRvcyBlIGNvbXBhcmFyIGNvbSBvcyBkYWRvcyBvYnNlcnZhZG9zLgoKYGBge3J9CnNpbTIgPC0gc2ltdWxhdGUobTIsIG5zaW0gPSA1MCkKcGxvdChkZW5zaXR5KGxvZyhob21lcyR0b3RhbHZhbHVlKSkpCmZvcihpIGluIDE6NTApbGluZXMoZGVuc2l0eShzaW0yW1tpXV0pLCBsdHkgPSAyLCBjb2wgPSAiZ3JleTgwIikKYGBgCgpPcyByZXN1bHRhZG9zIG7Do28gc8OjbyB0w6NvIHJ1aW5zIQoKRGlnYW1vcyBxdWUgZXN0ZWphbW9zIHNhdGlzZmVpdG9zIGNvbSBlc3RlIG1vZGVsby4gQ29tbyBpbnRlcnByZXRhbW9zIGFzIAplc3RpbWF0aXZhcyBkb3MgcGFyw6JtZXRyb3M/IENvbW8gYSB2YXJpw6F2ZWwgcmVzcG9zdGEgZm9pIHRyYW5zZm9ybWFkYSB1c2FuZG8gYSBmdW7Dp8OjbyBsb2dhcml0bW8gbmF0dXJhbCwgaW50ZXJwcmV0YW1vcyBhcyBlc3RpbWF0aXZhcyBkb3MgcGFyw6JtZXRyb3MgY29tbyBfZGlmZXJlbsOnYXMgcHJvcG9yY2lvbmFpcyBhcHJveGltYWRhc18uIAoKQWJhaXhvIHZlbW9zIG9zIGNvZWZpY2llbnRlcyBhcnJlZG9uZGFkb3MgcGFyYSA0IGNhc2FzIGRlY2ltYWlzLgoKCmBgYHtyfQpyb3VuZChjb2VmKG0yKSwgNCkKYGBgCgpFc3RhcyBzw6NvIHByb3BvcsOnw7Vlcy4gUGFyYSBvYnRlciBwb3JjZW50YWdlbnMsIG11bHRpcGxpcXVlIHBvciAxMDAuCgpgYGB7cn0Kcm91bmQoY29lZihtMiksIDQpICogMTAwCmBgYAoKQWxndW1hcyBpbnRlcnByZXRhw6fDtWVzIGLDoXNpY2FzOgoKLSBjYWRhIG1ldHJvIHF1YWRyYWRvIGNvbnN0cnXDrWRvIGFkaWNpb25hbCBhdW1lbnRhIG8gcHJlw6dvLCBlbSBtw6lkaWEsIAplbSAwLDA1JS4gT3UgbXVsdGlwbGlxdWUgcG9yIDEwMCBwYXJhIGRpemVyIHF1ZSBjYWRhIDEwMCBww6lzIHF1YWRyYWRvcyAKYWNhYmFkb3MgYWRpY2lvbmFpcyBhdW1lbnRhIG8gcHJlw6dvIGVtIDUlLgoKLSBjYWRhIHF1YXJ0byBhZGljaW9uYWwgYXVtZW50YSBvIHByZcOnbywgZW0gbcOpZGlhLCBlbSBjZXJjYSBkZSA0LDMlLgoKLSBjYWRhIGFjcmUgYWRpY2lvbmFsIGRlIHRhbWFuaG8gZGUgbG90ZSBhdW1lbnRhIG8gcHJlw6dvLCBlbSBtw6lkaWEsIAplbSBjZXJjYSBkZSAwLDQ3JS4KCkxlbWJyZS1zZSwgYSBpbnRlcnByZXRhw6fDo28gYXNzdW1lIHF1ZSBfdG9kYXMgYXMgb3V0cmFzIHZhcmnDoXZlaXMgc8OjbyBtYW50aWRhcyBjb25zdGFudGVzXyEKClVtYSBlc3RpbWF0aXZhIHVtIHBvdWNvIG1haXMgcHJlY2lzYSBwb2RlIHNlciBvYnRpZGEgX2V4cG9uZW5jaWFsaXphbmRvXyBvcyBjb2VmaWNpZW50ZXMgZSBkZXBvaXMgaW50ZXJwcmV0YW5kbyBvcyBlZmVpdG9zIGNvbW8gX211bHRpcGxpY2F0aXZvc18gZW0gCnZleiBkZSBhZGl0aXZvcy4gQWJhaXhvIGV4cG9uZW5jaWFtb3MgdXNhbmRvIGEgZnVuw6fDo28gYGV4cGAgZSBkZXBvaXMgCmFycmVkb25kYW1vcyBwYXJhIDQgY2FzYXMgZGVjaW1haXMuCgpgYGB7cn0Kcm91bmQoZXhwKGNvZWYobTIpKSwgNCkgCmBgYAoKUG9yIGV4ZW1wbG8sIGNhZGEgcXVhcnRvIGFkaWNpb25hbCAoYXNzdW1pbmRvIHF1ZSB0b2RhcyBhcyBkZW1haXMgdmFyacOhdmVpcyBwcmVkaXRvcmFzIHNlIG1hbnTDqW0gY29uc3RhbnRlKSBhdW1lbnRhIG8gcHJlw6dvIHRvdGFsIGVzcGVyYWRvIGVtIGNlcmNhIGRlIDQsNCUuIE11bHRpcGxpY2FyIHBvciAxLDA0MzkgZXF1aXZhbGUgYSBzb21hciA0LDM5JS4KClZhbW9zIHJldmlzYXIgbyByZXN1bHRhZG8gZG8gcmVzdW1vOgoKYGBge3J9CnN1bW1hcnkobTIpCmBgYAoKCioqVklTw4NPIEdFUkFMOioqCgotIFNlw6fDo28gZGUgcmVzw61kdW9zOiBhdmFsaWHDp8OjbyByw6FwaWRhIGRlIHJlc8OtZHVvcy4gSWRlYWxtZW50ZSwgbyBwcmltZWlybyBlIG8gdGVyY2VpcmEgcXVhcnRpcyBlIE1pbi9NYXggc2Vyw6NvIGFwcm94aW1hZGFtZW50ZSBlcXVpdmFsZW50ZXMgZW0gdmFsb3IgYWJzb2x1dG8uCgotIENvZWZpY2llbnRlczogbGlzdGEgb3MgY29lZmljaWVudGVzIGVzdGltYWRvcyBqdW50YW1lbnRlIGNvbSB0ZXN0ZXMgZGEgCmhpcMOzdGVzZSBudWxhIGRlIHF1ZSBjYWRhIGNvZWZpY2llbnRlIMOpIGVzdGF0aXN0aWNhbWVudGUgaWd1YWwgYSB6ZXJvLiAKRXN0L1NFID0gdmFsb3IgdC5cCgotIEVycm8gcGFkcsOjbyByZXNpZHVhbDogZXN0aW1hdGl2YSBkbyBkZXN2aW8gcGFkcsOjbyBjb25zdGFudGUgZGEgZGlzdHJpYnVpw6fDo28gCmRvcyByZXPDrWR1b3MsIHF1ZSBwb3IgaGlww7N0ZXNlIHNlZ3VlIHVtYSBkaXN0cmlidWnDp8OjbyBub3JtYWwuCgotIGdyYXVzIGRlIGxpYmVyZGFkZTogdGFtYW5obyBkYSBhbW9zdHJhIC0gbsO6bWVybyBkZSBjb2VmaWNpZW50ZXMgKDMwMjUgLSA0KQoKLSBSLXF1YWRyYWRvOiBwb3JjZW50YWdlbSBkYSB2YXJpw6JuY2lhIHRvdGFsIGRlIHkgZXhwbGljYWRhIHBlbG8gbW9kZWxvLgoKLSBFc3RhdMOtc3RpY2EgRjogdGVzdGUgZ2VyYWwgZGUgcXVlIHRvZG9zIG9zIGNvZWZpY2llbnRlcyAKKGV4Y2V0byBpbnRlcmNlcHRvKSBzw6NvIDAuCgpUb2RvcyBvcyB2YWxvcmVzLXAgcmVmZXJlbS1zZSBhb3MgdGVzdGVzIGRlIGhpcMOzdGVzZSBkZSBxdWUgb3MgY29lZmljaWVudGVzIHPDo28gaWd1YWlzIGEgemVyby4gTXVpdG9zIGVzdGF0w61zdGljb3MgZSBwZXNxdWlzYWRvcmVzIHByZWZlcmVtIG9ic2VydmFyIG9zIAppbnRlcnZhbG9zIGRlIGNvbmZpYW7Dp2EuCgpgYGB7cn0Kcm91bmQoY29uZmludChtMikgKiAxMDAsIDQpCmBgYAoKRGUgYWNvcmRvIGNvbSBvIG5vc3NvIG1vZGVsbywgY2FkYSBxdWFydG8gYWRpY2lvbmFsIGFjcmVzY2VudGEsIGVtIG3DqWRpYSwgCmVudHJlIDIlIGUgNiUgYW8gdmFsb3IgZGUgdW1hIGNhc2EsIGFzc3VtaW5kbyBxdWUgdG9kYXMgYXMgZGVtYWlzIHZhcmnDoXZlaXMgcHJlZGl0b3JhcyBzZSBtYW50w6ltIGNvbnN0YW50ZS4KCgojIyBRdWVzdMOjbyA0CgoxLiBFc2NyZXZhIGPDs2RpZ28gcGFyYSBtb2RlbGFyIGBsb2codG90YWx2YWx1ZSlgIGNvbW8gZnVuw6fDo28gZGUgYGZ1bGxiYXRoYCBlIGBmaW5zcWZ0LmAgQ2hhbWUgc2V1IG1vZGVsbyBkZSBgbTNgCgoKMi4gRXNjcmV2ZSBvIGPDs2RpZ28gcGFyYSBwcm9kdXppciBvcyBncsOhZmljb3MgZGUgZGlhZ27Ds3N0aWNvIGRvcyAKcmVzw61kdW9zCgoKMy4gQ29tbyBpbnRlcnByZXRhbW9zIG8gY29lZmljaWVudGUgZXN0aW1hZG8gZGEgdmFyacOhdmVpbCBgZnVsbGJhdGhgPwoKCjQuIEVzY3JldmEgY8OzZGlnbyBwYXJhIHNpbXVsYXIgb3MgZGFkb3MgZG8gbW9kZWxvLCBlbSBzZWd1aWRhLCBjb21wYXJlIGNvbSBvIGB0b3RhbHZhbHVlYCBvYnNlcnZhZG8uIEVzdGUgcGFyZWNlIHNlciB1bSBib20gbW9kZWxvPwoKCiMjIFByZWRpdG9yZXMgY2F0ZWfDs3JpY29zCgpWYW1vcyBhZGljaW9uYXIgYGhzZGlzdHJpY3RgIGFvIG1vZGVsbyBxdWUgYWNhYmFtb3MgZGUgYWp1c3Rhci4gRXN0YXIgZW0gdW0gZGV0ZXJtaW5hZG8gZGlzdHJpdG8gZXNjb2xhciBhZmV0YSBvIHZhbG9yIHRvdGFsIGRlIHVtYSBjYXNhPyAKCkEgZnVuw6fDo28gYHRhYmxlYCBwcm9kdXogdW1hIHRhYmVsYSBkZSBmcmVxdcOqbmNpYSAoY29udGFnZW0pIGRlIAp2YXJpw6F2ZWlzIGNhdGVnw7NyaWNhcyAoZSB0YW1iw6ltIGRlIG7Dum1lcm9zIGludGVpcm9zISk6CgpgYGB7cn0KdGFibGUoaG9tZXMkaHNkaXN0cmljdCkKYGBgCgpFc3NlcyBuw612ZWlzIG7Do28gc8OjbyBuw7ptZXJvcywgZW50w6NvIGNvbW8gUiBsaWRhIGNvbSBpc3NvIGVtIHVtIG1vZGVsbyBsaW5lYXI/IEEgbGluZ3VhZ2VtIGNyaWEgdW0gX2NvbnRyYXN0ZV8sIHF1ZSDDqSB1bWEgbWF0cml6IGRlIHplcm9zIGUgdW5zLiBTZSB2b2PDqiB0aXZlciB1bSBmYXRvciBjb20gSyBuw612ZWlzLCB0ZXLDoSBLLTEgY29sdW5hcy4gCgpOZXN0ZSBjYXNvIHRlcmVtb3MgZHVhcyBjb2x1bmFzOiB1bWEgcGFyYSBNb250aWNlbGxvIEhTIGUgb3V0cmEgcGFyYSBXZXN0ZXJuIEFsYmVtYXJsZSBIUy4gUG9yIHBhZHLDo28sIFIgcGVnYSBxdWFscXVlciBuw612ZWwgcXVlIHZlbmhhIHByaW1laXJvIGVtIG9yZGVtIGFsZmFiw6l0aWNhIGUgbyB0b3JuYSBvIG7DrXZlbCBfYmFzZWxpbmVfIG91IF9yZWZlcsOqbmNpYV8uCgpWZWphbW9zIG8gY29udHJhc3RlIHBhZHLDo28sIGNoYW1hZG8gX3RyZWF0bWVudCBjb250cmFzdF8uIFBhcmEgZmF6ZXIgaXNzbywgY29udmVydGVtb3MgYSB2YXJpw6F2ZWwvY29sdW5hIGBoc2Rpc3RyaWN0YCBlbSB1biBmYXRvciBlIGVudMOjbyB1c2Ftb3MgYSBmdW7Dp8OjbyBgY29udHJhc3RzKClgLiAKCk9CUzogTsOjbyBwcmVjaXNhbW9zIGZhemVyIGlzc28gcGFyYSBhZGljaW9uYXIgaHNkaXN0cmljdCBhbyBub3NzbyBtb2RlbG8hIEVzdGFtb3MgYXplbmRvIGlzc28gYXBlbmFzIHBhcmEgZ2VyYXIgYSAiZGVmaW5pw6fDo28iIGRlIGNvbnRyYXN0ZS4KCmBgYHtyfQpjb250cmFzdHMoZmFjdG9yKGhvbWVzJGhzZGlzdHJpY3QpKQpgYGAKCgpVbSBtb2RlbG8gY29tIGBoc2Rpc3RyaWN0YCB0ZXLDoSBkb2lzIGNvZWZpY2llbnRlczogYE1vbnRpY2VsbG9gIGUgYFdlc3Rlcm4gQWxiZW1hcmxlYAoKLSB1bWEgY2FzYSBubyBkaXN0cml0byBkZSBBbGJlbWFybGUgSFMgcmVjZWJlIGRvaXMgemVyb3MKLSB1bWEgY2FzYSBubyBkaXN0cml0byBkZSBNb250aWNlbGxvIEhTIHJlY2ViZSB1bSBuYSBjb2x1bmEgTW9udGljZWxsbwotIHVtYSBjYXNhIG5vIGRpc3RyaXRvIGRlIFdlc3Rlcm4gQWxiZW1hcmxlIEhTIHJlY2ViZSB1bWEgbmEgY29sdW5hIFdlc3QgQWxiCgpWYW1vcyBhanVzdGFyIG5vc3NvIG5vdm8gbW9kZWxvLgoKYGBge3J9Cm00IDwtIGxtKGxvZyh0b3RhbHZhbHVlKSB+IGZ1bGxiYXRoICsgZmluc3FmdCArIGhzZGlzdHJpY3QsIGRhdGEgPSBob21lcykKc3VtbWFyeShtNCkKYGBgCgpPcyBjb2VmaWNpZW50ZXMgcGFyYSBNb250aWNlbGxvIGUgV2VzdGVybiBBbGJlbWFybGUgc8OjbyBlbSByZWxhw6fDo28gYSBBbGJlbWFybGUgSFMuCgpgYGB7cn0Kcm91bmQoY29lZihtNCkgKiAxMDAsIDQpCmBgYAoKClBlbGFzIGVzdGltYXRpdmFzLCBvIHZhbG9yIGRlIHVtYSBjYXNhIGVtIFdlc3Rlcm4gQWxiZW1hcmxlIHNlcsOhIGNlcmNhIGRlIDEwJSBzdXBlcmlvciBhbyBkZSB1bWEgY2FzYSBlcXVpdmFsZW50ZSBlbSBBbGJlbWFybGUuIERhIG1lc21hIGZvcm1hLCBvIHZhbG9yIGRlIHVtYSBjYXNhIG5vIGRpc3RyaXRvIGRlIE1vbnRpY2VsbG8gc2Vyw6EgY2VyY2EgZGUgNyUgaW5mZXJpb3IgYW8gZGUgdW1hIGNhc2EgCmVxdWl2YWxlbnRlIG5vIGRpc3RyaXRvIGRlIEFsYmVtYXJsZS4KCgojIyBRdWVzdMOjbyA0CgoxLiBFc2NyZXZhIG8gY8OzZGlnbyBwYXJhIG1vZGVsYXIgYGxvZyh0b3RhbHZhbHVlKWAgY29tbyBmdW7Dp8OjbyBkZSBgZnVsbGJhdGhgLCBgZmluc3FmdGAgZSBgY29vbGluZ2AuIENoYW1lIHNldSBtb2RlbG8gZGUgYG01YC4KCgoyLiBRdWFsIMOpIGEgaW50ZXJwcmV0YcOnw6NvIGRvIGNvZWZpY2VudGUgZGUgYGNvb2xpbmdgPwoKCiMjIE1vZGVsYW5kbyBJbnRlcmHDp8O1ZXMKCkVtIG5vc3NvIG1vZGVsbyBhY2ltYSwgcXVlIGluY2x1w61hIGBoc2Rpc3RyaWN0YCwgYXNzdW1pbW9zIHF1ZSBvcyBlZmVpdG9zIGVyYW0gKmFkaXRpdm9zKi4gUG9yIGV4ZW1wbG8sIG7Do28gaW1wb3J0YXZhIGVtIHF1ZSBkaXN0cml0byBlc2NvbGFyIGEgY2FzYSBmaWNhdmEsIG8gZWZlaXRvIGRlIGBiYW5oZWlyb2Agb3UgYGZpbnNxZnRgIGVyYSBvIG1lc21vLiAKCk9zIGVmZWl0b3Mgc2VyZW0gYWRpdGl2b3MsIHRhbWLDqW0gaW1wbGljYSBxdWUgbyBlZmVpdG8gZGUgY2FkYSDigJxiYW5oZWlybyBjb21wbGV0b+KAnSBhZGljaW9uYWwgZXJhIG8gbWVzbW8sIGluZGVwZW5kZW50ZW1lbnRlIGRvIHRhbWFuaG8gZGEgY2FzYSwgZSB2aWNlLXZlcnNhLiBJc3NvIHBvZGUgc2VyIG11aXRvIHNpbXBsaXN0YS4KCkFzIGludGVyYcOnw7VlcyBwZXJtaXRlbSBxdWUgb3MgZWZlaXRvcyBkYXMgdmFyacOhdmVpcyBkZXBlbmRhbSBkZSBvdXRyYXMgdmFyacOhdmVpcy4gTm92YW1lbnRlIG8gY29uaGVjaW1lbnRvIGRvIGFzc3VudG8gYXV4aWxpYSBuYSBwcm9wb3Npw6fDo28gZGUgaW50ZXJhw6fDtWVzLiBDb21vIHZlcmVtb3MsIGFzIGludGVyYcOnw7VlcyB0b3JuYW0gc2V1IG1vZGVsbyBtYWlzIGZsZXjDrXZlbCwgbWFzIG1haXMgZGlmw61jaWwgZGUgZW50ZW5kZXIuCgpSIHNpbXBsaWZpY2EgYSBpbmNsdXPDo28gZGUgaW50ZXJhw6fDtWVzIGVtIG1vZGVsb3MuIEJhc3RhIGluZGljYXIgdW1hIGludGVyYcOnw6NvIGVudHJlIGR1YXMgdmFyacOhdmVpcyBjb2xvY2FuZG8gZG9pcyBwb250b3MgKDopIGVudHJlIGVsYXMuIEFiYWl4byBpbmNsdcOtbW9zIGludGVyYcOnw7VlcyBiaWRpcmVjaW9uYWlzLiAoVm9jw6ogcG9kZSB0ZXIgaW50ZXJhw6fDtWVzIGRlIHRyw6pzIHZpYXMgZSBzdXBlcmlvcmVzLCBtYXMgZWxhcyBzw6NvIG11aXRvIGRpZsOtY2VpcyBkZSBpbnRlcnByZXRhci4pCgpgYGB7cn0KbTYgPC0gbG0obG9nKHRvdGFsdmFsdWUpIH4gZnVsbGJhdGggKyBmaW5zcWZ0ICsgaHNkaXN0cmljdCArIAogICAgICAgICAgIGZ1bGxiYXRoOmZpbnNxZnQgKyBmdWxsYmF0aDpoc2Rpc3RyaWN0ICsgCiAgICAgICAgICAgZmluc3FmdDpoc2Rpc3RyaWN0LCBkYXRhID0gaG9tZXMpCnN1bW1hcnkobTYpCmBgYAoKQSBpbnRlcnByZXRhw6fDo28gw6kgbXVpdG8gbWFpcyBkaWbDrWNpbC4gTsOjbyBwb2RlbW9zIGludGVycHJldGFyIGRpcmV0YW1lbnRlIG9zIF9lZmVpdG9zIHByaW5jaXBhaXNfIGRlIGBmdWxsYmF0aGAsIGBmaW5zcWZ0YCBvdSBgaHNkaXN0cmljdGAuIEVsZXMgaW50ZXJhZ2VtLiBRdWFsIMOpIG8gZWZlaXRvIGRlIGBmaW5zcWZ0YD8gRWxlIGRlcGVuZGUgZGUgYGZ1bGxiYXRoYCBlIGBoc2Rpc3RyaWN0YC4KCkFzIGludGVyYcOnw7VlcyBzw6NvIOKAnHNpZ25pZmljYXRpdmFz4oCdIG91IG5lY2Vzc8Ohcmlhcz8gUG9kZW1vcyB1c2FyIGEgZnVuw6fDo28gYGFub3ZhYCBwYXJhIGF2YWxpYXIgZXN0YSBxdWVzdMOjby4gRXNzYSBmdW7Dp8OjbyBleGVjdXRhIHVtYSBzw6lyaWUgZGUgX3Rlc3RlcyBGIHBhcmNpYWlzXy4gQ2FkYSBsaW5oYSBhYmFpeG8gw6kgdW0gdGVzdGUgZGUgaGlww7N0ZXNlLiAKCkEgaGlww7N0ZXNlIG51bGEgw6kgcXVlIG8gbW9kZWxvIGNvbSBlc3RlIHByZWRpdG9yIMOpIGlndWFsIGFvIG1vZGVsbyBzZW0gbyBwcmVkaXRvci4gT3MgdGVzdGVzIGFub3ZhIGFiYWl4byB1c2FtIG8gcXVlIMOpIGNoYW1hZG8gZGUgc29tYXMgZGUgcXVhZHJhZG9zIGRvIFRpcG8gSS4gSXNzbyByZXNwZWl0YSBhIG9yZGVtIGRhcyB2YXJpw6F2ZWlzIG5vIG1vZGVsby4gQXNzaW06CgotIG8gcHJpbWVpcm8gdGVzdGUgY29tcGFyYSB1bSBtb2RlbG8gY29tIGFwZW5hcyB1bSBpbnRlcmNlcHRvIGEgdW0gbW9kZWxvIGNvbSBpbnRlcmNlcHRvIGUgY29tIGEgdmFyacOhdmVsIGBmdWxsYmF0aGAuCgotIG8gc2VndW5kbyB0ZXN0ZSBjb21wYXJhIHVtIG1vZGVsbyBjb20gaW50ZXJjZXB0byBlIGBmdWxsYmF0aGAgY29tIHVtIG1vZGVsbyBjb20gaW50ZXJjZXB0bywgYGZ1bGxiYXRoYCBlIGBmaW5zcWZ0YC4KCi0gRSBhc3NpbSBwb3IgZGlhbnRlLgoKU2UgYSBoaXDDs3Rlc2UgbnVsYSBmb3IgdmVyZGFkZWlyYSwgbyB2YWxvciBGIGRldmUgZXN0YXIgcHLDs3hpbW8gZGUgMS4KCmBgYHtyfQphbm92YShtNikKYGBgCgpBIGludGVyYcOnw6NvIGBmaW5zcWZ0OmhzZGlzdHJpY3RgIG7Do28gcGFyZWNlIGNvbnRyaWJ1aXIgbXVpdG8gcGFyYSBvIG1vZGVsby4KCk8gZmF0byBkZSB1bWEgaW50ZXJhw6fDo28gc2VyIHNpZ25pZmljYXRpdmEgbsOjbyBpbXBsaWNhIG5lY2Vzc2FyaWFtZW50ZSBxdWUgc2VqYSByZWx2YW50ZSBvdSBxdWUgdmFsaGEgYSBwZW5hIGluY2x1w60tbGEgbm8gbW9kZWxvLiBOw6NvIHBvZGVtb3MgaW5mZXJpciBuYWRhIApzb2JyZSBhIG5hdHVyZXphIGRhIGludGVyYcOnw6NvIGEgcGFydGlyIGRhIHRhYmVsYSBkYSBBbsOhbGlzZSBkYSAKVmFyacOibmNpYSAoQU5PVkEpLgoKX0dyw6FmaWNvcyBkZSBlZmVpdG9zXyBwb2RlbSBub3MgYWp1ZGFyIGEgdmlzdWFsaXphciBlIGRhciBzZW50aWRvIGFvcyAKbW9kZWxvcyBjb20gaW50ZXJhw6fDtWVzLiBWYW1vcyBmYXplciB1bSB1c2FuZG8gbyBwYWNvdGUgZ2dlZmZlY3RzIGUgYW5saXNhciAKbyBxdWUgZWxlIGVzdMOhIGV4aWJpbmRvLgoKYGBge3J9CmxpYnJhcnkoZ2dlZmZlY3RzKQpwbG90KGdncHJlZGljdChtNiwgdGVybXMgPSBjKCJmdWxsYmF0aCIsICJoc2Rpc3RyaWN0IikpKQojIGNvbG9jYSBmdWxsYmF0aCBubyBlaXhvIHgsIGFncnVwYSBwb3IgaHNkaXN0cmljdApgYGAKClF1YWwgw6kgbyBlZmVpdG8gZGUgYGZ1bGxiYXRoYD8gRGVwZW5kZS4gw4kgbWFpcyBmb3J0ZSBlbSBXZXN0ZXJuIEFsYmVtYXJsZSBlIE1vbnRpY2VsbG8uIMOJIGNsYXJvIHF1ZSBncmFuZGUgcGFydGUgZGEgZGlmZXJlbsOnYSBvY29ycmUgZW0gdmFsb3JlcyBleHRyZW1vcwpkZSBgZnVsbGJhdGhgLiBBcyDigJxmYWl4YXPigJ0gYW8gcmVkb3IgZGFzIGxpbmhhcyByZXByZXNlbnRhbSBpbnRlcnZhbG9zIGNvbSA5NSUgY29uZmlhbsOnYSAuCgpPIHF1ZSBleGF0YW1lbnRlIGZvaSBwbG90YWRvPyBQb2RlbW9zIHZlciB1c2FuZG8gYSBmdW5jw6NvIGBnZ3ByZWRpY3RgIApzZW0gYHBsb3RgOgoKYGBge3J9CmdncHJlZGljdChtNiwgdGVybXMgPSBjKCJmdWxsYmF0aCIsICJoc2Rpc3RyaWN0IikpCmBgYAoKYGdncHJlZGljdGAgdXNvdSBub3NzbyBtb2RlbG8gcGFyYSBmYXplciBwcmV2aXPDtWVzIGRlIGB0b3RhbHZhbHVlYCBwYXJhIHbDoXJpb3MgdmFsb3JlcyBkZSBgZnVsbGJhdGhgIG5vcyB0csOqcyBkaXN0cml0b3MgZXNjb2xhcmVzLCBtYW50ZW5kbyAKYGZpbnNxZnRgIGlndWFsIGEgMTgyOCAoYSBtZWRpYW5hIGRlIGBmaW5zcWZ0YCkuCgpQb2RlbW9zIGVzcGVjaWZpY2FyIG9zIHZhbG9yZXMgc2UgcXVpc2VybW9zLiBQb3IgZXhlbXBsbywgY3JpZSB1bSBncsOhZmljbyAKZGUgZWZlaXRvIHBhcmEgMSBhIDUgYmFuaGVpcm9zIGUgbWFudGVuaGEgYGZpbnNxZnRgIGVtIDIuMDAwOgoKYGBge3J9CnBsb3QoZ2dwcmVkaWN0KG02LCB0ZXJtcyA9IGMoImZ1bGxiYXRoWzE6NV0iLCAiaHNkaXN0cmljdCIpLCAKICAgICAgICAgIGNvbmRpdGlvbiA9IGMoZmluc3FmdCA9IDIwMDApKSkKYGBgCgpFIHF1YW50byBhb3MgZWZlaXRvcyBkZSBgZmluc3FmdGAgZSBgZnVsbGJhdGhgPyBFc3RhIMOpIF91bWEgaW50ZXJhw6fDo28gZGUgZHVhcyB2YXJpw6F2ZWlzIG51bcOpcmljYXNfLiBBIHNlZ3VuZGEgdmFyacOhdmVsIGRldmUgc2VydmlyIGNvbW8gdmFyacOhdmVsIGRlIAphZ3J1cGFtZW50byBhbyBjcmlhciB1bSBncsOhZmljbyBkZSBlZmVpdG8uIEFiYWl4byBkZWZpbmltb3MgYGZ1bGxiYXRoYCAKXHBhcmEgYXNzdW1pciB2YWxvcmVzIGRlIDIgYSA1IGUgYGZpbnNxZnRgIHBhcmEgYXNzdW1pciB2YWxvcmVzIGRlIDEwMDAgCmEgNDAwMCBlbSBwYXNzb3MgZGUgNTAwLgoKYGBge3J9CnBsb3QoZ2dwcmVkaWN0KG02LCB0ZXJtcyA9IGMoImZpbnNxZnRbMTAwMDo0MDAwIGJ5PTUwMF0iLCAiZnVsbGJhdGhbMjo1XSIpKSkKYGBgCgpPIGVmZWl0byBkZSBgZmluc3FmdGAgcGFyZWNlIGRpbWludWlyIGVtIGZ1bsOnw6NvIGRvIG7Dum1lcm8gZGUgYmFuaGVpcm9zIApjb21wbGV0b3MgcXVlIHVtYSBjYXNhIHRpdmVyLiBNYXMgZXhpc3RlbSBwb3VjYXMgY2FzYXMgZ3JhbmRlcyBjb20gMiBiYW5oZWlyb3MgY29tcGxldG9zIGUsIGRhIG1lc21hIGZvcm1hLCBwb3VjYXMgY2FzYXMgcGVxdWVuYXMgY29tIDUgYmFuaGVpcm9zIGNvbXBsZXRvcy4gCkVtYm9yYSBhIGludGVyYcOnw6NvIHNlamEg4oCcc2lnbmlmaWNhdGl2YeKAnSBubyBtb2RlbG8sIMOpIGNsYXJhbWVudGUgdW1hIGludGVyYcOnw6NvIAptdWl0byBwZXF1ZW5hLgoKCiMjIFF1ZXN0w6NvIDUKCjEuIEVzY3JldmEgY8OzZGlnbyBwYXJhIG1vZGVsYXIgYGxvZyh0b3RhbHZhbHVlKWAgY29tbyBmdW7Dp8OjbyBkZSBgZnVsbGJhdGhgLCBgZmluc3FmdGAsIGBjb29saW5nYCBlIGEgaW50ZXJhw6fDo28gZW50cmUgYGZpbnNxZnRgIGUgYGNvb2xpbmdgLiBDaGFtZSBzZXUgCm1vZGVsbyBkZSBgbTdgLiBBIGludGVyYcOnw6NvIMOpIGltcG9ydGFudGU/CgoyLiBWaXN1YWxpemUgYSBpbnRlcmHDp8OjbyB1c2FuZG8gYSBmdW7Dp8OjbyBgZ2dwcmVkaWN0YC4gVXNlIGBbMTAwMDo0MDAwIGJ5PTUwMF1gIApwYXJhIGRlZmluaXIgbyBpbnRlcnZhbG8gZGUgYGZpbnNxZnRgIG5vIGVpeG8geC4gUXXDo28gbm90w6F2ZWwgw6kgZXNzYSBpbnRlcmHDp8Ojbz8KCgojIyBFZmVpdG9zIE7Do28gTGluZWFyZXMKCkF0w6kgYWdvcmEgYXNzdW1pbW9zIHF1ZSBhIHJlbGHDp8OjbyBlbnRyZSB1bSBwcmVkaXRvciBlIGEgcmVzcG9zdGEgw6kgX2xpbmVhcl8gCihwb3IgZXhlbXBsbywgcGFyYSB1bWEgbXVkYW7Dp2EgZGUgMSB1bmlkYWRlIGVtIHVtIHByZWRpdG9yLCBhIHJlc3Bvc3RhIG11ZGEgCmVtIHVtIHZhbG9yIGZpeG8pLiAKCkVzc2Egc3Vwb3Npw6fDo28gw6BzIHZlemVzIHBvZGUgc2VyIHNpbXBsaXN0YSBlIHBvdWNvIHJlYWxpc3RhLiBGZWxpem1lbnRlLCAKZXhpc3RlbSBtYW5laXJhcyBkZSBhanVzdGFyIGVmZWl0b3MgbsOjbyBsaW5lYXJlcyBlbSB1bSBtb2RlbG8gbGluZWFyLgoKQXF1aSBlc3TDoSB1bSBleGVtcGxvIHLDoXBpZG8gZGUgZGFkb3MgbsOjbyBsaW5lYXJlcyBzaW11bGFkb3M6IHVtIHBvbGluw7RtaW8gCmRlIDLCuiBncmF1LgoKYGBge3J9CnggPC0gc2VxKGZyb20gPSAtMTAsIHRvID0gMTAsIGxlbmd0aC5vdXQgPSAxMDApCnNldC5zZWVkKDMpCnkgPC0gMS4yICsgMip4ICsgMC45KnheMiArIHJub3JtKDEwMCwgbWVhbiA9IDAsIHNkID0gMTApCm5sX2RhdCA8LSBkYXRhLmZyYW1lKHksIHgpCnBsb3QoeSB+IHgsIG5sX2RhdCkKYGBgCgrDiSBldmlkZW50ZSBxdWUgdW0gbW9kZWxvIGxpbmVhciBuw6NvIGZ1bmNpb25hcsOhIGJlbSBwYXJhIGVzdGVzIGRhZG9zLiBBIApyZWxhw6fDo28gZW50cmUgeCBleSAgbsOjbyDDqSBhZGVxdWFkYW1lbnRlIGNhcHR1cmFkYSBwb3IgdW0gbW9kZWxvIGxpbmVhci4KClNlIHF1aXPDqXNzZW1vcyB0ZW50YXIgInJlY3VwZXJhciIgb3MgY29lZmljZWludGVzIHF1ZSB1c2Ftb3MgbmEgc2ltdWxhw6fDo28gCmRlc3NlcyBkYWRvcywgcG9kZXLDrWFtb3MgYWp1c3RhciB1bSBtb2RlbG8gcG9saW5vbWlhbCB1c2FuZG8gYSBmdW7Dp8OjbyAKYHBvbHkoKWAgbmEgc2ludGF4ZSBkYSBmw7NybXVsYToKCmBgYAojIyBDb2RpZ28gYXBlbmFzIGVmZWl0b3MgaWx1c3RyYXRpdm9zCm5sbTEgPC0gbG0oeSB+IHBvbHkoeCwgZGVncmVlID0gMiwgcmF3ID0gVFJVRSksIGRhdGEgPSBubF9kYXQpCmBgYAoKTm8gZW50YW50bywgYSBhYm9yZGFnZW0gcmVjb21lbmRhZGEgcGFyYSBhanVzdGFyIGVmZWl0b3MgbsOjbyBsaW5lYXJlcyDDqSB1c2FyIF9zcGxpbmVzIG5hdHVyYWlzXyBlbSB2ZXogZGUgcG9saW7DtG1pb3MuIFNwbGluZXMgbmF0dXJhaXMgZXNzZW5jaWFsbWVudGUgbm9zIHBlcm1pdGVtIGFqdXN0YXIgdW1hIHPDqXJpZSBkZSBwb2xpbsO0bWlvcyBjw7piaWNvcyBjb25lY3RhZG9zIGVtIG7Ds3MgbG9jYWxpemFkb3MgCm5vIGludGVydmFsbyBkZSBub3Nzb3MgZGFkb3MuCgpBIG9ww6fDo28gbWFpcyBmw6FjaWwgw6kgdXNhciBhIGZ1bsOnw6NvIGBucygpYCBkbyBwYWNvdGUgc3BsaW5lcywgcXVlIHZlbSBpbnN0YWxhZG8gCmNvbSBSLiBgbnNgIHNpZ25pZmljYSAibmF0dXJhbCBzcGxpbmVzIi4gTyBzZWd1bmRvIGFyZ3VtZW50byBzw6NvIG9zIGdyYXVzIGRlIGxpYmVyZGFkZSAoYGRmYCkuIFBvZGUgc2VyIMO6dGlsIHBlbnNhciBlbSBgZGZgIGNvbW8gbyBuw7ptZXJvIGRlIHZlemVzIHF1ZSBhIApsaW5oYSBzdWF2ZSBtdWRhIGRlIGRpcmXDp8Ojby4KCkZyYW5rIEhhcnJlbGwgYWZpcm1hIGVtIHNldSBsaXZybyBfUmVncmVzc2lvbiBNb2RlbCBTdHJhdGVnaWVzXyBxdWUgMyBhIDUgYGRmYCAKw6kgcXVhc2Ugc2VtcHJlIHN1ZmljaWVudGUuIFNldSBjb25zZWxobyBiw6FzaWNvIMOpIGFsb2NhciBtYWlzIGBkZmAgcGFyYSAKdmFyacOhdmVpcyBxdWUgdm9jw6ogY29uc2lkZXJhIG1haXMgaW1wb3J0YW50ZXMuCgpWYW1vcyB2ZXIgY29tbyBmdW5jaW9uYSBjb20gbm9zc29zIGRhZG9zIHNpbXVsYWRvcy4KCmBgYHtyfQpsaWJyYXJ5KHNwbGluZXMpCm5sbTIgPC0gbG0oeSB+IG5zKHgsIGRmID0gMiksIGRhdGEgPSBubF9kYXQpCnN1bW1hcnkobmxtMikKYGBgCgrDiSBpbXBvc3PDrXZlbCBpbnRlcnByZXRhciBvIHJlc3VsdGFkbyBkYSBmdW7Dp8OjbyBgc3VtbWFyeSgpYC4gQXNzaW0sIHZhbW9zIAp2aXN1YWxpemFyIG8gYWp1c3RlIGNvbSB1bSBncsOhZmljbyBkZSBlZmVpdG8uCgpgYGB7cn0KbGlicmFyeShnZ2VmZmVjdHMpCnBsb3QoZ2dwcmVkaWN0KG5sbTIsIHRlcm1zID0gIngiKSwgYWRkLmRhdGEgPSBUUlVFKQpgYGAKCgpWYW1vcyB2b2x0YXIgYW9zIGRhZG9zIGRhcyBjYXNhcyBlIGFqdXN0YXIgdW0gZWZlaXRvIG7Do28gbGluZWFyIHBhcmEgYGZpbnNxZnRgIHVzYW5kbyB1bSBzcGxpbmUgbmF0dXJhbCBjb20gYGRmYCBpZ3VhbCBhIDUuIEFiYWl4bywgaW5jbHXDrW1vcyAKdGFtYsOpbSBgaHNkaXN0cmljdGAgZSBgbG90c2l6ZWAgZSBwZXJtaXRpbW9zIHF1ZSBgZmluc3FmdGAgZSBgaHNkaXN0cmljdGAgCmludGVyYWphbS4KCmBgYHtyfQpubG0zIDwtIGxtKAogIGxvZyh0b3RhbHZhbHVlKSB+IG5zKGZpbnNxZnQsIDUpICsgaHNkaXN0cmljdCArIGxvdHNpemUgKwogICAgbnMoZmluc3FmdCwgNSk6aHNkaXN0cmljdCwKICBkYXRhID0gaG9tZXMKKQpgYGAKCgpBIGZ1bsOnw6NvIGBhbm92YWAgcGVybWl0ZSBhdmFsaWFyIG8gZWZlaXRvIG7Do28gbGluZWFyIGUgYSBpbnRlcmHDp8Ojby4gQWxndW0gCnRpcG8gZGUgaW50ZXJhw6fDo28gZW50cmUgYGZpbnNxZnRgIGUgYGhzZGlzdHJpY3RgIHBhcmVjZSBlc3RhciBwcmVzZW50ZS4KCmBgYHtyfQphbm92YShubG0zKQpgYGAKCgpOb3ZhbWVudGUsIMOpIGltcG9zc8OtdmVsIGFuYWxpc2FyIG9zIHJlc3VsdGFkb3MgcmV0b3JuYWRvcyBwZWxhIApmdW7Dp8OjbyBgc3VtbWFyeWAuCgpgYGB7cn0Kc3VtbWFyeShubG0zKQpgYGAKCgpPcyBncsOhZmljb3MgZGUgZWZlaXRvcyBzw6NvIG5vc3NhIMO6bmljYSBlc3BlcmFuw6dhIHBhcmEgY29tcHJlZW5kZXIgZXN0ZSBtb2RlbG8uIEFiYWl4bywgcGxvdGFtb3MgbyB2YWxvciB0b3RhbCBwcmV2aXN0byBlbSBmaW5xZnQgdmFyaWFuZG8gZGUgMS4wMDAgYSAzLjAwMCwgYWdydXBhZG8gcG9yIGhzZGlzdHJpY3QuCgpgYGB7cn0KcGxvdChnZ3ByZWRpY3QobmxtMywgdGVybXMgPSBjKCJmaW5zcWZ0WzEwMDA6MzAwMCBieT0yNTBdIiwgImhzZGlzdHJpY3QiKSkpCmBgYAoKTyBlZmVpdG8gZGUgYGZpbnNxZnRgIG5vIGB2YWxvciB0b3RhbGAgcGFyZWNlIG1haXMgZm9ydGUgZW0gV2VzdGVybiAKQWxiZW1hcmxlIHF1YW5kbyB2b2PDqiB1bHRyYXBhc3NhIDEuNTAwIHDDqXMgcXVhZHJhZG9zLgoKTyBtb2RlbG8gc2ltdWxhIGRhZG9zIHNlbWVsaGFudGVzIGVtIGRpc3RyaWJ1acOnw6NvIGFvcyBkYWRvcyBvYnNlcnZhZG9zPwoKYGBge3J9CnNpbTQgPC0gc2ltdWxhdGUobmxtMywgbnNpbSA9IDUwKQpwbG90KGRlbnNpdHkobG9nKGhvbWVzJHRvdGFsdmFsdWUpKSkKZm9yKGkgaW4gMTo1MClsaW5lcyhkZW5zaXR5KHNpbTRbW2ldXSksIGNvbCA9ICJncmV5ODAiKQpgYGAKCkFpbmRhIGRldmVtb3MgdmVyaWZpY2FyIGFzIGhpcMOzdGVzZXMgc29icmUgb3MgcmVzw61kdW9zIGRvIG1vZGVsby4KCmBgYHtyfQpwbG90KG5sbTMpCmBgYAoKQXMgY2FzYXMgMTIsIDQwLCA5NjMgZSAxODEwIHBhcmVjZW0gc2UgZGVzdGFjYXIuIFZhbW9zIGRhciB1bWEgb2xoYWRhLgoKYGBge3J9CmggPC0gYygxMiwgNDAsIDk2MywgMTgxMCkKaG9tZXNbaCxjKCJ0b3RhbHZhbHVlIiwgImZpbnNxZnQiLCAibG90c2l6ZSIpXQpgYGAKCkFzIGNhc2FzIDEyIGUgNDAgdMOqbSB2YWxvciB0b3RhbCBtdWl0byBiYWl4byBlIG8gbW9kZWxvIHN1cGVyZXN0aW1hIHNldXMgCnZhbG9yZXMuIEEgY2FzYSA5NjMgdGVtIHVtIHZhbG9yIHRvdGFsIGVub3JtZSBjb20gMCBhY3JlcyBkZSB0YW1hbmhvIGRlIGxvdGUuIApBIGNhc2EgMTgxMCBvY3VwYSA2MTEgYWNyZXMgZSBlc3NlIHZhbG9yIHRlbSB1bWEgZ3JhbmRlIGluZmx1w6puY2lhIGVtIHNldSAKdmFsb3IgYWp1c3RhZG8uCgpgYGB7cn0KY2JpbmQob2JzZXJ2ZWQgPSBob21lcyR0b3RhbHZhbHVlW2hdLCBmaXR0ZWQgPSBleHAoZml0dGVkKG5sbTMpW2hdKSkKYGBgCgoKIyMgUXVlc3TDo28gNgoKMS4gRXNjcmV2YSBjw7NkaWdvIHBhcmEgbW9kZWxhciBgbG9nKHRvdGFsdmFsdWUpYCBjb21vIGZ1bsOnw6NvIGRlIGBmaW5zcWZ0YCAKY29tIHVtIHNwbGluZSBuYXR1cmFsIGNvbSBgZGYgPSA1YCwgYGNvb2xpbmdgLCBlIGEgaW50ZXJhw6fDo28gZGUgYGNvb2xpbmdgIGUgYGZpbnNxZnRgIChzcGxpbmUgbmF0dXJhbCBjb20gYGRmID0gNWApLiBDaGFtZSBzZXUgbW9kZWxvIGRlIGBubG00YC4KCgoyLiBVc2UgYSBmdW7Dp8OjbyBgYW5vdmFgIHBhcmEgdmVyaWZpY2FyIHNlIGEgaW50ZXJhw6fDo28gcGFyZWNlIG5lY2Vzc8OhcmlhLiAKTyBxdWUgdm9jw6ogYWNoYT8KCgozLiBDcmllIHVtIGdyw6FmaWNvIGRlIGVmZWl0byBkZSBgZmluc3FmdGAgcG9yIGByZXNmcmlhbWVudG9gLiBUZW50ZSBgWzEwMDA6NTAwMCBieT0yNTBdYCBwYXJhIG8gaW50ZXJ2YWxvIGRlIHZhbG9yZXMgZGUgYGZpbnNxZnRgLgoKCiMjIEZpbQoKRXN0YSBhdGl2aWRhZGUgdGV2ZSBjb21vIG9iamV0aXZvIG1vc3RyYXIgYSB2b2PDqnMgb3MgZnVuZGFtZW50b3MgZGEgbW9kZWxhZ2VtIApsaW5lYXIgZW0gUi4gRXNwZXJhbW9zIHF1ZSB2b2PDqiB0ZW5oYSB1bWEgY29tcHJlZW5zw6NvIG1lbGhvciBkZSBjb21vIGZ1bmNpb25hIAphIG1vZGVsYWdlbSBsaW5lYXIuIAoKTyBxdWUgZml6ZW1vcyBob2plIGZ1bmNpb25hIHBhcmEgX3Jlc3VsdGFkb3MgbnVtw6lyaWNvcyBpbmRlcGVuZGVudGVzXy4gClTDrW5oYW1vcyB1bWEgb2JzZXJ2YcOnw6NvIHBvciBjYXNhIGUgYSBub3NzYSByZXNwb3N0YSBmb2kg4oCcdmFsb3IgdG90YWzigJ0gCihgdG90YWx2YWx1ZWApLCB1bSBuw7ptZXJvLiBOb3Nzb3MgbW9kZWxvcyByZXRvcm5hcmFtIG8gdmFsb3IgdG90YWwgX23DqWRpb18gCmVzcGVyYWRvLCBkYWRvcyB2w6FyaW9zIHByZWRpdG9yZXMuIEVzdGUgw6kgdW0gZGVzaWduIGJhc3RhbnRlIHNpbXBsZXMuCgpBcyBjb2lzYXMgZmljYW0gbWFpcyBjb21wbGljYWRhcyBxdWFuZG8gdm9jw6ogdGVtLCBkaWdhbW9zLCByZXNwb3N0YXMgYmluw6FyaWFzIApvdSBtw7psdGlwbGFzIG1lZGlkYXMgc29icmUgYSBtZXNtYSBvYnNlcnZhw6fDo28uIFVtYSBsaXN0YSBuw6NvIGV4YXVzdGl2YSBkZSAKb3V0cm9zIHRpcG9zIGRlIG1vZGVsb3MgaW5jbHVpOgoKLSBtb2RlbG9zIGxpbmVhcmVzIGdlbmVyYWxpemFkb3MgKHBhcmEgcmVzcG9zdGFzIGJpbsOhcmlhcyBlIGRlIGNvbnRhZ2VtKQoKLSBtb2RlbG9zIGxvZ2l0IG11bHRpbm9taWFpcyAocGFyYSByZXNwb3N0YXMgY2F0ZWfDs3JpY2FzKQoKLSBtb2RlbG9zIGxvZ2l0IG9yZGVuYWRvcyAocGFyYSByZXNwb3N0YXMgY2F0ZWfDs3JpY2FzIG9yZGVuYWRhcykKCi0gbW9kZWxvcyBsaW5lYXJlcyBsb25naXR1ZGluYWlzIG91IGRlIGVmZWl0byBtaXN0byAocGFyYSByZXNwb3N0YXMgY29tIAptw7psdGlwbGFzIG1lZGlkYXMgc29icmUgbyBtZXNtbyBhc3N1bnRvIG91IGdydXBvcyBkZSBtZWRpZGFzIHJlbGFjaW9uYWRhcykKCi0gbW9kZWxvcyBkZSBzb2JyZXZpdsOqbmNpYSAocGFyYSByZXNwb3N0YXMgcXVlIG1lZGVtIG8gdGVtcG8gYXTDqSB1bSBldmVudG8pCgotIG1vZGVsb3MgZGUgc8OpcmllcyB0ZW1wb3JhaXMgKHBhcmEgcmVzcG9zdGFzIHF1ZSBhcHJlc2VudGFtLCBkaWdhbW9zLCB2YXJpYcOnw6NvIHNhem9uYWwgYW8gbG9uZ28gZG8gdGVtcG8pCgoKKipSZWZlcsOqbmNpYXMqKgoKLSBGYXJhd2F5LCBKLiAoMjAwNSkuIF9MaW5lYXIgTW9kZWxzIGluIFJfLiBMb25kb246IENoYXBtYW4gJiBIYWxsLgotIEZveCwgSi4gKDIwMDIpLiBfQSBSIGFuZCBTLVBsdXMgQ29tcGFuaW9uIHRvIEFwcGxpZWQgUmVncmVzc2lvbl8uIExvbmRvbjogCiAgU2FnZS4KLSBIYXJyZWxsLCBGLiAoMjAxNSkuIF9SZWdyZXNzaW9uIE1vZGVsaW5nIFN0cmF0ZWdpZXNfICgybmQgZWQuKS4gTmV3IFlvcms6IFNwcmluZ2VyLgotIEt1dG5lciwgTS4sIGV0IGFsLiAoMjAwNSkuIF9BcHBsaWVkIExpbmVhciBTdGF0aXN0aWNhbCBNb2RlbHNfICg1dGggZWQuKS4gCk5ldyBZb3JrOiBNY0dyYXctSGlsbC4KLSBNYWluZG9uYWxkIEouLCBCcmF1biwgSi5XLiAoMjAxMCkuIF9EYXRhIEFuYWx5c2lzIGFuZCBHcmFwaGljcyBVc2luZyBSXyAKKDNyZCBlZC4pLiBDYW1icmlkZ2U6IENhbWJyaWRnZSBVbml2IFByZXNzLgoKCgojIyBFeHRyYTogRGlyZXRyaXplcyBwYXJhIHRyYW5zZm9ybWHDp8OjbyBkZSB2YXJpw6F2ZWlzCgpOYSBtb2RlbGFnZW0gYW50ZXJpb3IsIGFwbGljYW1vcyBhIHRyYW5zZm9ybWHDp8OjbyBsb2dhcml0bWljYSBlbSBgdG90YWx2YWx1ZWAgCnBhcmEgcmVkdXppciBhIGFzc2ltZXRyaWEgZGEgZGlzdHJpYnVpw6fDo28gZSwgcG9ydGFudG8sIHBhcmEgYWp1ZGFyIGEgYXRlbmRlciAKw6BzIGhpcMOzdGVzZXMgZG8gbW9kZWxvIGRlIHJlZ3Jlc3PDo28gbGluZWFyIGNsw6Fzc2ljYS4gCgpMZW1icmUtc2UgZGUgcXVlLCBzZW0gYSB0cmFuc2Zvcm1hw6fDo28gbG9nYXLDrXRtaWNhLCBub3Nzb3MgcmVzw61kdW9zIGVyYW0gCmdyYW5kZXMgZSBhc3NpbcOpdHJpY29zLCBvIHF1ZSDDqSB1bWEgbWFuZWlyYSBlbGVnYW50ZSBkZSBkaXplciBxdWUgbm9zc28gCm1vZGVsbyBuw6NvIHBvc3N1w61hIGJvbSBhanVzdGUgYW9zIGRhZG9zLiBVbSBib20gbW9kZWxvIGRldmUgdGVyIHJlc8OtZHVvcyByZWxhdGl2YW1lbnRlIHBlcXVlbm9zIGNvbSBkaXNwZXJzw6NvIHNpbcOpdHJpY2EuCgpVbWEgdHJhbnNmb3JtYcOnw6NvIGxvZ2Fyw610bWljYSBmYXppYSBzZW50aWRvIHBvciBkb2lzIG1vdGl2b3M6CgoxLiBBIHZhcmnDoXZlbCByZXNwb3N0YSBgdG90YWx2YWx1ZWAgZXJhIGVzdHJpdGFtZW50ZSBwb3NpdGl2YSwgdGluaGEgdW0gCmxpbWl0ZSBzdXBlcmlvciBncmFuZGUgZSBjb2JyaWEgdsOhcmlhcyBvcmRlbnMgZGUgZ3JhbmRlemEuCgoyLiBhcyBtdWRhbsOnYXMgZW0gYHRvdGFsdmFsdWVgIGRlIGFjb3JkbyBjb20gb3MgcHJlZGl0b3JlcyBmb3JhbSByZWxhdGl2YXMgKG11bHRpcGxpY2F0aXZhcykgZSBuw6NvIGFic29sdXRhcyAoYWRpdGl2YXMpLCBvIHF1ZSBjb3JyZXNwb25kZSDDoCBlc2NhbGEgCmxvZ2Fyw610bWljYSBuYXR1cmFsLgoKw4kgaW1wb3J0YW50ZSBvYnNlcnZhciBxdWUgbmVtIHRvZGFzIGFzIHZhcmnDoXZlaXMgY29tIGRpc3RyaWJ1acOnw6NvIGFzc2ltw6l0cmljYSBwcmVjaXNhbSBzZXIgdHJhbnNmb3JtYWRhcyBxdWFuZG8gc2UgdHJhdGEgZGUgbW9kZWxhZ2VtIApsaW5lYXIuIEFzIGhpcMOzdGVzZXMgZGlzdHJpYnV0aXZhcyBzw6NvIGZlaXRhcyBlbSByZWxhw6fDo28gYW9zIHJlc8OtZHVvcyAKbsOjbyBlbSByZWxhw6fDo28gw6BzIHZhcmnDoXZlaXMgcmVzcG9zdGEgZSBwcmVkaXRvcmVzLiAKCk5vIGVudGFudG8sIHBvZGUgaGF2ZXIgbW9tZW50b3MgZW0gcXVlIHZvY8OqIHByZWNpc2UgaW52ZXN0aWdhciBvdXRyYXMgCnRyYW5zZm9ybWHDp8O1ZXMgYWzDqW0gZGEgbG9nYXLDrXRtaWNhLiBFc3NhcyB0cmFuc2Zvcm1hw6fDtWVzIGFsdGVybmF0aXZhcywgCmF1YXNlIHNlbXByZSBhc3N1bWVtIGEgZm9ybWEgZGUgdW1hIF90cmFuc2Zvcm1hw6fDo28gZGUgcG90w6puY2lhXyAob3Ugc2VqYSwgCmVsZXZhLXNlIGEgdmFyacOhdmVsIGEgdW1hIHBvdMOqbmNpYSB1c2FuZG8gdW0gZXhwb2VudGUpLiAKCkFzIHBvdMOqbmNpYXMgc8OjbyBnZXJhbG1lbnRlIHNpbWJvbGl6YWRhcyBwZWxhIGxldHJhIGdyZWdhIGxhbWJkYSAoJFxsYW1iZGEkKS4gCkNvbW8gdW1hIHBvdMOqbmNpYSBpZ3VhbCAwLCB0ZW1vcyBhIHRyYW5zZm9ybWHDp8OjbyBsb2dhcsOtdG1pY2EuCgpEaWdhbW9zIHF1ZSBhIHZhcmnDoXZlbCBzZWphIGB5YC4gVW1hIHBhbGV0YSBiw6FzaWNhIGRlIHBvc3PDrXZlaXMgdHJhbnNmb3JtYcOnw7VlcyAKZGUgcG9kZXIgaW5jbHVpOgoKLSDOuyA9IC0xICgkMS95JCkKLSDOuyA9IC0wLDUgKCQxIC8gXHNxcnQoeSkkKQotIM67ID0gMCAoJGxvZyh5KSQpCi0gzrsgPSAwLDUgKCRzcXJ0KHkpJCkKLSDOuyA9IDEgeSAoc2VtIHRyYW5zZm9ybWHDp8OjbykKLSDOuyA9IDIgICgkeV4yJCkKCkEgZnVuw6fDo28gYHN5bWJveCgpYCBkbyBwYWNvdGUgYGNhcmAgY3JpYSB1bWEgYXZhbGlhw6fDo28gdmlzdWFsIGRlIHF1YWwgCnBvdMOqbmNpYSB0b3JuYSBhIGRpc3RyaWJ1acOnw6NvIHJhem9hdmVsbWVudGUgc2ltw6l0cmljYS4gCgpBYmFpeG8sIHF1YW5kbyBhIHVzYW1vcyBlbSBgdG90YWx2YWx1ZWAsIHZlbW9zIHF1ZSBhIHRyYW5zZm9ybWHDp8OjbyAKbG9nYXLDrXRtaWNhICjOuyA9IDApIGZheiBvIG1lbGhvciB0cmFiYWxobyBlbSB0b3JuYXIgYSBkaXN0cmlidWnDp8OjbyBtYWlzIApzaW3DqXRyaWNhLgoKYGBge3J9CmNhcjo6c3ltYm94KGhvbWVzJHRvdGFsdmFsdWUpCmBgYAoKVGFtYsOpbSBwb2RlbW9zIHVzYXIgYHN5bWJveCgpYCBlbSB1bSBvYmpldG8gbW9kZWxvLiBQb3IgZXhlbXBsbywgaXNzbyBwcm9kdXogZXNzZW5jaWFsbWVudGUgbyBtZXNtbyBncsOhZmljbyB1c2FuZG8gb3MgcmVzw61kdW9zIGRvIG1vZGVsbyBlbSB2ZXogZG8gCmB2YWxvciB0b3RhbGAuIFNpbXBsZXNtZW50ZSBjYW5hbGl6ZSBvIG1vZGVsbyBwYXJhIGBzeW1ib3goKWAuCgpgYGB7cn0KbG0odG90YWx2YWx1ZSB+IGZpbnNxZnQgKyBiZWRyb29tICsgbG90c2l6ZSwgZGF0YSA9IGhvbWVzKSB8PgogIGNhcjo6c3ltYm94KCkKYGBgCgpVbWEg4oCcYnVzY2HigJ0gcGVsYSDigJxtZWxob3LigJ0gdHJhbnNmb3JtYcOnw6NvIGRlIHBvdMOqbmNpYSBwb2RlIHNlciByZWFsaXphZGEgCmNvbSBhIGZ1bsOnw6NvIGBwb3dlclRyYW5zZm9ybSgpYCwgdGFtYsOpbSBubyBwYWNvdGUgY2FyLiBBIHByw6F0aWNhIHVzdWFsIMOpIApjb252ZXJ0ZXIgbyByZXN1bHRhZG8gcGFyYSBhIHBvdMOqbmNpYSBzaW1wbGVzIG1haXMgcHLDs3hpbWEgbGlzdGFkYSBhY2ltYS4gClBvciBleGVtcGxvLCBwb2RlbW9zIGNhbmFsaXphciBvIHJlc3VsdGFkbyBkbyBtb2RlbG8gcGFyYSBgcG93ZXJUcmFuc2Zvcm0oKWAgCmUgdmVyIHF1ZSBhICJtZWxob3IiIHRyYW5zZm9ybWHDp8OjbyDDqSBjZXJjYSBkZSAwLDE2LgoKYGBge3J9CmxtKHRvdGFsdmFsdWUgfiBmaW5zcWZ0ICsgYmVkcm9vbSArIGxvdHNpemUsIGRhdGEgPSBob21lcykgfD4KICBjYXI6OnBvd2VyVHJhbnNmb3JtKCkgCmBgYAoKMCwxNiBlc3TDoSBwcsOzeGltbyBkZSAwLCBlbnTDo28gZmF6IHNlbnRpZG8gcHJvc3NlZ3VpciBjb20gdW1hIHRyYW5zZm9ybWHDp8OjbyBsb2dhcsOtdG1pY2EuIElzc28gc2ltcGxpZmljYSBtdWl0byBhIGludGVycHJldGHDp8Ojby4KCg==